tf2.0学习(五)——Keras高层接口

前边介绍了TensorFlow的基本操作:
tf2.0学习(一)——基础知识
tf2.0学习(二)——进阶知识
tf2.0学习(三)——神经网络
tf2.0学习(四)——反向传播算法
下面介绍一个高度模块化、易扩展的高层接口Keras。

5.0 简介

Keras是一个有python开发的神经网络计算库。是一个高度模块化和易扩展的高层神经网络接口。它分为前端和后端,其中后端一般是用现有的神经网络框架实现底层计算,前端接口是经过Keras抽象后的统一的函数接口。用户使用Keras框架轻松实现不同底层框架的切换,灵活性较高。

Tensorflow2.0开始,Keras被认定为唯一的高层接口,取代了原来Tensorflow1中的tf.layers等高层接口。

5.1 常见功能模块

Keras提供了很多高层的神经网络相关类和函数。包括数据集加载、网络层类、模型容器、损失函数类、优化器类、经典模型类等等。

5.1.1 常见网络层

对于常见的神经网络层,可以使用张量形式的底层接口函数来实现,这些接口函数一般在tf.nn模块中。更常用的是用层的方式实现,在tf.keras.layers模块下提供了大量的神经网层,如全连接层、激活函数层、卷积层、池化层、循环神经网络层等。对于这些网络层类,只需要在创建时指定网络层的相关参数,并调用call方法即可完成前向计算。再调用call函数是,会自动调用没层的前向传播逻辑,前向传播逻辑一般在实现类的call函数里。

以softmax层为例,它既可以使用tf.nn.softmax函数在前向传播中完成Softmax计算,也可以通过tf.keras.layers.Softmax(axis)来搭建神经网络层。

import tensorflow as tf 

x = tf.constant([2,1,4,2,5], dtype=tf.float16)
layer = tf.keras.layers.Softmax(axis=-1)
layer(x)

<tf.Tensor: shape=(5,), dtype=float32, numpy=
array([0.03350928, 0.01232738, 0.247602  , 0.03350928, 0.673052  ],
      dtype=float32)>

5.1.2 网络容器

对于常见的神经网络,往往都会有很多层。前向传播的过程需要数据通过每个层来实现,这时候如果手动调用每一层的类的话,代码会变的非常臃肿。因此Keras提供了网络容器Sequential,他可以将多个网络层封装成一个大的网络模型,从而只需要调用一次网络模型的前向传播,就完成了从第一层到最后一层的全部计算。

import tensorflow as tf 
model = tf.keras.Sequential([
    tf.keras.layers.Dense(128),
    tf.keras.layers.ReLU(),
    tf.keras.layers.Dense(10),
    tf.keras.layers.Softmax()
])

x = tf.random.uniform([10, 50])


model(x)
<tf.Tensor: shape=(10, 10), dtype=float32, numpy=
array([[0.05376878, 0.17156708, 0.16427904, 0.05504481, 0.04971287,
        0.09335194, 0.10940206, 0.11748007, 0.11103256, 0.07436065],
       [0.09636729, 0.14340015, 0.10765289, 0.06640282, 0.04153357,
        0.08176894, 0.10070642, 0.14419483, 0.13028036, 0.0876927 ],
       [0.10896047, 0.12469008, 0.13271534, 0.05778752, 0.03720576,
        0.10271669, 0.10986929, 0.14858323, 0.10459764, 0.07287396],
       [0.0665472 , 0.15090697, 0.18803081, 0.06644785, 0.04090692,
        0.09305567, 0.0954863 , 0.1445031 , 0.08430488, 0.06981032],
       [0.09652282, 0.08426037, 0.12721734, 0.0804379 , 0.07161396,
        0.10512593, 0.11527853, 0.17215529, 0.08922189, 0.05816597],
       [0.07605492, 0.18164131, 0.16475034, 0.06142522, 0.04285333,
        0.09868649, 0.10680319, 0.11706878, 0.09897676, 0.0517397 ],
       [0.10844021, 0.13180628, 0.14759877, 0.0680831 , 0.04360767,
        0.07573178, 0.11283645, 0.14127326, 0.08165637, 0.08896605],
       [0.09385677, 0.14408283, 0.17832078, 0.07523569, 0.04950608,
        0.07810812, 0.10581949, 0.13110453, 0.08575373, 0.0582121 ],
       [0.07820774, 0.1099428 , 0.17537114, 0.04206308, 0.05659465,
        0.08852449, 0.10071502, 0.18280421, 0.09930225, 0.0664746 ],
       [0.07563203, 0.08918475, 0.13077222, 0.06964436, 0.04555722,
        0.13870038, 0.09354863, 0.15792203, 0.11947975, 0.0795586 ]],
      dtype=float32)>

model.summary()
Model: "sequential"
_________________________________________________________________
Layer (type)                 Output Shape              Param #
=================================================================
dense (Dense)                (10, 128)                 6528
_________________________________________________________________
re_lu (ReLU)                 (10, 128)                 0
_________________________________________________________________
dense_1 (Dense)              (10, 10)                  1290
_________________________________________________________________
softmax (Softmax)            (10, 10)                  0
=================================================================
Total params: 7,818
Trainable params: 7,818
Non-trainable params: 0
_________________________________________________________________

5.2 模型装配、训练与测试

在训练网络时,一般的流程是先通过前向计算获得网络的输出值,在通过损失函数计算网络误差,然后通过自动求导工具计算梯度并利用梯度下降法更新惨呼,同时间隔性的测试网络性能。这些训练逻辑,一般都可以通过Keras提供的高层借口,简介清晰的实现。

5.2.1 模型装配

Keras中有两个比较特殊的类,分别是tf.keras.layers.Layer 和 tf.keras.layers.Model。其中tf.keras.layers.Layer是所有网络层的母类,它定义了网络层的一些常用功能,包括添加权值,管理权值列表等工程。Model是所有网络的母类,除了包含Layer类的工程外,还包括保存模型、加载模型、训练模型等便捷功能。其中Sequential也是Model的子类。

我们以Sequential为例,介绍一下模型的装配,首先创建一个5层的神经网络。

model = tf.keras.Sequential([
    tf.keras.layers.Dense(256, activation='relu'),
    tf.keras.layers.Dense(125, activation='relu'),
    tf.keras.layers.Dense(64, activation='relu'),
    tf.keras.layers.Dense(32, activation='relu'),
    tf.keras.layers.Dense(10)
])

model.build(input_shape=[4, 784])
model.summary()

Model: "sequential"
_________________________________________________________________
Layer (type)                 Output Shape              Param #
=================================================================
dense (Dense)                (4, 256)                  262400
_________________________________________________________________
dense_1 (Dense)              (4, 125)                  32125
_________________________________________________________________
dense_2 (Dense)              (4, 64)                   8064
_________________________________________________________________
dense_3 (Dense)              (4, 32)                   2080
_________________________________________________________________
dense_4 (Dense)              (4, 10)                   330
=================================================================
Total params: 304,999
Trainable params: 304,999
Non-trainable params: 0
_________________________________________________________________

创建模型之后,正常流程就是对数据集迭代多个Epoch,每次按batch_size分批进行训练,对数据前向计算,计算误差,反向传播自动求导,更新参数。由于这部分的逻辑高度通用,所以在Keras中提供了compile()和fit()函数方便实现上述逻辑。

compile()函数用于指定优化器,损失函数,评价指标等,这一步成为装配。

optim = tf.keras.optimizers.Adam(lr=0.001)
loss = tf.keras.losses.CategoricalCrossentropy(from_logits=True)
model.compile(optimizer = optim, loss=loss, metrics=['accurary'])

5.2.2 模型训练

模型装配完成之后,还要通过fit()函数传入指定的训练集,验证集,这一步被称为模型的训练。

def preprocess(x, y):
    y= tf.cast(y, dtype=tf.int32)
    y= tf.one_hot(y, depth=10)
        y = tf.reshape(y, [-1, 10])
    x= tf.cast(x, dtype=tf.float32) / 255
        x = tf.reshape(x, [-1, 784])
    return x, y


(x, y), (x_test, y_test) = tf.keras.datasets.mnist.load_data()
train_db = tf.data.Dataset.from_tensor_slices((x, y))
val_db=tf.data.Dataset.from_tensor_slices((x_test, y_test))

train_db = train_db.map(preprocess)
val_db = val_db.map(preprocess)

history = model.fit(train_db, epochs=5, validation_data=val_db, validation_freq=2)

之后可以调用history.history,打印训练记录。

5.2.3 模型测试

模型测试只需要调用model.predict(x)即可。

还可以使用model.evaluate(db)循环测试db数据集上所有的样本,并打印性能指标。

5.3 模型的保存与加载

模型训练完成后一般需要将其保存起来,一遍后续的模型测试和部署。并且,由于大规模神经网络的训练,往往耗费数天或数周的时间,一旦训练过程中发生意外,为了避免导致之前的训练成果丢失,间隔性的保存模型是个非常好的习惯。如果训练过程中发生死机等意外,可以从最近保存的模型开始重新训练,从而避免大量浪费资源和时间。
Keras提供了三种模型的保存和加载方法。

5.3.1 张量方式

网络的状态主要体现在网络的机构和网络内部的张量上,因此在拥有网络结构的前提下,直接保存网络张量是最轻量级的保存方式。通过调用model.save_weights(path)的方法既可以将当前的网络参数保存到指定path目录下。

model.save_weights('weights.ckpt')
del model
model = tf.keras.Sequential([
    tf.keras.layers.Dense(256, activation='relu'),
    tf.keras.layers.Dense(125, activation='relu'),
    tf.keras.layers.Dense(64, activation='relu'),
    tf.keras.layers.Dense(32, activation='relu'),
    tf.keras.layers.Dense(10)
])
optim = tf.keras.optimizers.Adam(lr=0.001)
loss = tf.keras.losses.CategoricalCrossentropy(from_logits=True)
model.compile(optimizer = optim, loss=loss, metrics=['accurary'])

model.load_weights('weights.ckpt')

这种方式保存、加载网络最为轻量级,保存的文件中只保留了网络参数的数值,并没有保存网络的机构信息。但是只有有用相同的网络结构才能将保存的模型恢复成原来的网络状态。

5.3.2 网络方式

下面介绍一种不需要网络结构也能恢复成原网络的方式。通过调用model.save(path)方法,可以将网络结构和模型参数保存到path上。

model.save('model.h5')
model = tf.keras.models.load_model('model.h5')

model.h5文件,不仅保存了网络的参数信息,还保存另外网络的结构,因此在加载该网络的时候,并不需要提前创建网络模型。

5.3.3 SaveModel方式

TensorFlow之所以被产业界青睐,除了优秀的神经网络高层接口外,还得益于其强大的生态系统,包括网页端和移动端的支持。当我们需要将模型部署到其他平台式,采用TensorFlow提出来的SaveModel方式可以保证平台无关性。

tf.saved_model.save(model, 'model-savedmodel')
model = tf.saved_model.load('model-savedmodel')

5.4 自定义网络

尽管Keras提供了很多常用网络,但深度学习领域可以使用的网络层远远不止这些,科研工作者一般是先自行实现较为新颖的网络层,再通过大量的实验验证有效后,框架才会跟进,内置这些网络层。
对于需要自定义的网络层,可以通过自定义类实现。自定义的网络层,需要继承tf.keras.layers.Layer,自定义的网络需要继承自tf.keras.Model。这样定义出来的网络和网络层,能更方便的使用Layer/Model提供的参数管理功能,也更方便和其他已经定义好的网络进行交互。

5.4.1 自定义网络层

对于自定义的网络层,除了要继承自tf.keras.layers.Layer外,至少还需要实现初始化init方法和前向传播call方法。下面以全连接网络为例(没有骗置)。

class MyDense(tf.keras.layers.Layer):
    def __init__(self, input_dim, output_dim):
        super(MyDense, self).__init__()
        self.w = tf.Variable(tf.random.normal((input_dim, output_dim)), trainable=True)
    def call(self, inputs):
        out = inputs @ self.w 
        out = tf.nn.relu(out)
        return out

以上代码为,实现了一个没有偏置的全连接层,并加入了relu激活函数。

5.4.2 自定义网络

在完成了自定义网络层之后,我们通过Sequential实现一个网络模型:

model = tf.keras.Sequential([
    MyDense(784, 256),
    MyDense(256, 128),
    MyDense(128, 64),
    MyDense(64, 32),
    MyDense(32, 10),
])

model.build(input_shape=(None, 784))

model.summary()

Model: "sequential_1"
_________________________________________________________________
Layer (type)                 Output Shape              Param #
=================================================================
my_dense_5 (MyDense)         (None, 256)               200704
_________________________________________________________________
my_dense_6 (MyDense)         (None, 128)               32768
_________________________________________________________________
my_dense_7 (MyDense)         (None, 64)                8192
_________________________________________________________________
my_dense_8 (MyDense)         (None, 32)                2048
_________________________________________________________________
my_dense_9 (MyDense)         (None, 10)                320
=================================================================
Total params: 244,032
Trainable params: 244,032
Non-trainable params: 0
_________________________________________________________________

Sequential容器适合数据顺序的从第一层到最后一层进行传播的网络。然而对于复杂网络,例如第三层的输入不仅是第二层的输出,还有第一层的输出,此时用Sequential容器就不能实现了。需要自定义更加灵活的网络。

class MyModel(tf.keras.Model):
    def __init__(self):
        self.fc1 = MyDense(784, 256),
        self.fc2 = MyDense(256, 128),
        self.fc3 = MyDense(128, 64),
        self.fc4 =  MyDense(64, 32),
        self.fc5 =  MyDense(32, 10)

    def call(self, inputs):
        x = self.fc1(inputs)
        x = self.fc2(x)
        x = self.fc3(x)
        x = self.fc4(x)
        x = self.fc5(x)
        return x

5.5 模型乐园

对于一些经典的网络模型,譬如ResNet,VGG等,不需要手动创建网络,可以直接从tf.keras.applications子模块中,通过一行代码即可创建并使用这些经典模型,同事还可以设置weights参数加载预训练的网络模型,非常方便。

5.5.1 加载模型

以ResNet50为例,一般讲ResNet50去除最后一层之后的网络作为新任务的特征提取网络,即用在ImageNet数据集上训练好的网络参数初始化,并根据自定义的任务,在后边追加一个分类器或子网络。
首先加载ResNet50:

resnet = tf.keras.applications.ResNet50(weights='imagenet', include_top=False)
resnet.summary()

include_top=False,就是去掉该网络的最后一层,此时的网络输出为(b, 7, 7, 2048),常备拿来当做特征提取器。对于某个特定的任务,可以在其之后加上子网络。

新建一个池化层(高、宽维度的下采样),将特征从(b, 7, 7, 2048)变为(b, 2048),再接一个全连接层,将数据映射成100维。

global_average_layer = tf.keras.layers.GlobalAveragePooling2D()
fc = tf.keras.layers.Dense(100)

然后我们把以上子网络风女装成一个全新的网络。

mynet = tf.keras.Sequential([resnet, global_average_layer, fc])

 mynet.summary()
Model: "sequential_2"
_________________________________________________________________
Layer (type)                 Output Shape              Param #
=================================================================
resnet50 (Functional)        (None, None, None, 2048)  23587712
_________________________________________________________________
global_average_pooling2d (Gl (None, 2048)              0
_________________________________________________________________
dense_1 (Dense)              (None, 100)               204900
=================================================================
Total params: 23,792,612
Trainable params: 23,739,492
Non-trainable params: 53,120
_________________________________________________________________

通过resnet.trainable = False可以冻结ResNet部分的网络参数,在训练的时候,只更新后边的网络层。

5.6 测量工具

在网络训练过程中,我们经常会关心准确率、召回率等指标。除了可以手动计算外,Keras提供了一些常用工具,位于tf.keras.metrics模块中。

5.6.1 新建测量工具

tf.keras.metrics中提供了很多测量器类,包括Mean,Accuracy,ConsineSimilarity等。

loss_meter = tf.keras.metrics.Mean()

5.6.2 写入数据

loss_meter.update_state(float(loss))

5.6.3 读入统计信息

loss_meter.result()

5.6.4 清除状态

loss_meter.reset_status()

5.6.5 准确率统计实战

acc_meter = tf.keras.metrics.Accuracy()

x = tf.random.uniform((4, 223, 223, 3))
out = mynet(x)

out.shape
TensorShape([4, 100])

y = [61, 61, 0, 61]
pred = tf.argmax(out, axis=1)

acc_meter.update_state(y, pred)
acc_meter.result().numpy()
0.75

acc_meter.reset_states()

5.7 可视化

在训练过程中,通过web端远程监控网络的训练进度,可视化网络的训练结果,是非常重要的。TensorFlow提供了专门的可视化工具,叫TensorBoard。它通过TensorFlow将监控数据写入文件系统,并利用web端监控对应的文件系统。

5.7.1 模型端

在模型端,通过tf.summary.create_file_writer创建监控数据的Summary类。

summary_writer = tf.summary.create_file_writer(log_dir)
with summary_writer.as_default():
    tf.summary.scalar('train-loss', float(loss), step=step)

TensorBoard通过字符串id监控不同类别的数据,因此对于loss,我们将它命名为train-loss,其他类型的数据最好不要写入,防止数据污染。

5.7.2 浏览器端

如何开启web后端查看呢,需要在终端运行 tensorboard --logdir path指定监控文件的path,即可打开web浏览器进行监控了。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350

推荐阅读更多精彩内容