心法利器[3] | tf.keras自学笔记
【前沿重器】
全新栏目,本栏目主要和大家一起讨论近期自己学习的心得和体会,与大家一起成长。具体介绍:仓颉专项:飞机大炮我都会,利器心法我还有。
往期回顾
说起来自己还挺菜的,tensorflow很久之前就已经把keras合并到自己的生态里,虽然早就会了keras,但是因为排期的原因自己一直没有更新这块的知识,这次系统地学习一下,并记录下来。
当然,我不想写成一个教程,也不想整成一个API,更希望这是一个导学,能给大家一些学习的思路,学这个之前,希望大家还是要对深度学习和tensorflow有些了解。下面讲的实质性知识的东西,全部来源于tensorflow的源码和官网版本的API文档。给个链接吧:
tensorflow源码:https://github.com/tensorflow tensorflow1.15.0文档:https://tensorflow.google.cn/versions/r1.15/api_docs/python/tf/keras/wrappers tensorflow keras文档:https://tensorflow.google.cn/guide/keras
我的目标是学的1.14,但是1.14文档因为一些原因我好像上不去,所以我主要参考的是1.15的文档,在更新公告上没有对keras进行太大幅度的更新,因此不用太担心。
tf.keras生态
keras本身是一套tensorflow老版本的优化方案,由于其便捷性一直受到大家欢迎,因此google将其收编也是大势所趋,合并的具体版本并不清楚,但是在1.14版本已经成型,因此我以这个版本为基础进行自己的学习,有API更新的我后续也会跟进。
接着说tf.keras,最简单的了解一个包,其实就是用help了,我们来看看help(tensorflow.keras)
会发生什么,内容比较多,这里我只想把这玩意下属的包给列举出来,特殊的我加点解释:
PACKAGE CONTENTS
activations (package) # 激活函数
applications (package) # 应用,预装的一些固定结构,例如mobilenet
backend (package) # 后端,底层函数,类似绝对值、点积之类的都有
callbacks (package) # 回调函数接口
constraints (package) # 训练过程中对函数的约束,如MaxNorm
datasets (package) # 数据集操作
estimator (package) # 基于keras构造的estimator类
experimental (package) # 一些实验模块会放在这里,例如学习率变化策略
initializers (package) # 初始化工具
layers (package) # 各种深度学习的层
losses (package) # 各种损失函数定义
metrics (package) # 各种评估指标,可用于训练过程监测
mixed_precision (package)
models (package) # 就是keras里的model类,组合各种层形成模型
optimizers (package) # 优化器
preprocessing (package) # 预处理工具
regularizers (package) # 正则化
utils (package) # 各种工具函数
wrappers (package) # 实现多个工具共通,目前实现了sklearn的
可见tf.keras实现了大量的功能,形成了相对完备的深度学习生态,这个完整的框架能让我们轻松实现深度学习。
当然看API学习本身缺少系统性,API更适合学完之后的深入学习或者是平时的词典查阅,学习还是要系统性的。
建模框架
如何写模型应该是初学者最关心的问题,keras建模主要有两种模式,分别是序列式和函数式。
序列式建模
链接:https://tensorflow.google.cn/guide/keras/sequential_model
序列式建模是keras最基本的结构,简单的理解就是和搭积木一样一个接着一个的堆叠就好了,来看看例子:
# Define Sequential model with 3 layers
model = keras.Sequential(
[
layers.Dense(2, activation="relu", name="layer1"),
layers.Dense(3, activation="relu", name="layer2"),
layers.Dense(4, name="layer3"),
]
)
# Call model on a test input
x = tf.ones((3, 3))
y = model(x)
通过keras.Sequential
将每一个层堆叠起来,这个堆叠的实际上就是keras.layers
的对象,如上图所示就是3个全连接层,这样子堆叠相比古老的tensorflow.nn
就避免了计算每一层输入输出的维数的问题,很方便。
当然,除了直接构造一个keras.layers
对象向量直接放入Sequential
之外,还可以用add
的方式进行放入。
model = keras.Sequential()
model.add(layers.Dense(2, activation="relu"))
model.add(layers.Dense(3, activation="relu"))
model.add(layers.Dense(4))
构造完之后,如果想看看自己的建模内容,可以用``model.summary`查看并汇总,上面的模型执行后的效果如下:
Model: "sequential_3"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
dense_7 (Dense) (1, 2) 10
_________________________________________________________________
dense_8 (Dense) (1, 3) 9
_________________________________________________________________
dense_9 (Dense) (1, 4) 16
=================================================================
Total params: 35
Trainable params: 35
Non-trainable params: 0
函数式建模
函数式建模是keras有一种建模框架,文档:https://tensorflow.google.cn/guide/keras/functional。
函数式是一种更为灵活的模式,对于更复杂的网络,就可以用它来整。来看一个完整的例子:
inputs = keras.Input(shape=(784,))
dense = layers.Dense(64, activation="relu")
x = dense(inputs)
x = layers.Dense(64, activation="relu")(x)
outputs = layers.Dense(10)(x)
model = keras.Model(inputs=inputs, outputs=outputs, name="mnist_model")
首先初始化了一个输入点,然后后面构造的dense
对象,这个对象是可调用的(内部其实就是有一个call函数),调用了这个inputs
,这就代表了在inputs
后接了一个dense
层,一个接着一个,就能实现整体建模,我们用``model.summary`看看:
Model: "mnist_model"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
input_1 (InputLayer) [(None, 784)] 0
_________________________________________________________________
dense (Dense) (None, 64) 50240
_________________________________________________________________
dense_1 (Dense) (None, 64) 4160
_________________________________________________________________
dense_2 (Dense) (None, 10) 650
=================================================================
Total params: 55,050
Trainable params: 55,050
Non-trainable params: 0
_________________________________________________________________
如我们所料就是一个完整网络。
有了网络,就可以开始训练和预测了,整个过程也不复杂:
# 加载数据
(x_train, y_train), (x_test, y_test) = keras.datasets.mnist.load_data()
# 数据预处理
x_train = x_train.reshape(60000, 784).astype("float32") / 255
x_test = x_test.reshape(10000, 784).astype("float32") / 255
# 模型编译
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=2, validation_split=0.2)
# 模型效果评估
test_scores = model.evaluate(x_test, y_test, verbose=2)
print("Test loss:", test_scores[0])
print("Test accuracy:", test_scores[1])
刚才说到,函数式能构造更为复杂的模型,我们来看一个例子,我们先看一张模型的结构图:
可以清楚看到,模型有多个输入和多个输出,这种模型结构就只能用函数式来处理了。
num_tags = 12 # Number of unique issue tags
num_words = 10000 # Size of vocabulary obtained when preprocessing text data
num_departments = 4 # Number of departments for predictions
# 模型的输入部分,一共3个输入
title_input = keras.Input(
shape=(None,), name="title"
) # Variable-length sequence of ints
body_input = keras.Input(shape=(None,), name="body") # Variable-length sequence of ints
tags_input = keras.Input(
shape=(num_tags,), name="tags"
) # Binary vectors of size `num_tags`
# 对模型的输入分别进行处理
# Embed each word in the title into a 64-dimensional vector
title_features = layers.Embedding(num_words, 64)(title_input)
# Embed each word in the text into a 64-dimensional vector
body_features = layers.Embedding(num_words, 64)(body_input)
# Reduce sequence of embedded words in the title into a single 128-dimensional vector
title_features = layers.LSTM(128)(title_features)
# Reduce sequence of embedded words in the body into a single 32-dimensional vector
body_features = layers.LSTM(32)(body_features)
# 处理以后将他们拼接起来
# Merge all available features into a single large vector via concatenation
x = layers.concatenate([title_features, body_features, tags_input])
# Stick a logistic regression for priority prediction on top of the features
priority_pred = layers.Dense(1, name="priority")(x)
# Stick a department classifier on top of the features
department_pred = layers.Dense(num_departments, name="department")(x)
# 整理结果,完成输出
# Instantiate an end-to-end model predicting both priority and department
model = keras.Model(
inputs=[title_input, body_input, tags_input],
outputs=[priority_pred, department_pred],
)
当然的,构造好模型以后,就需要编译模型,定义好训练方式,针对这种多目标的问题还要设计好对应的权重,一般的有两种方式:
# 向量形式的损失函数定制
model.compile(
optimizer=keras.optimizers.RMSprop(1e-3),
loss=[
keras.losses.BinaryCrossentropy(from_logits=True),
keras.losses.CategoricalCrossentropy(from_logits=True),
],
loss_weights=[1.0, 0.2],
)
# 字典形式的损失函数定制
model.compile(
optimizer=keras.optimizers.RMSprop(1e-3),
loss={
"priority": keras.losses.BinaryCrossentropy(from_logits=True),
"department": keras.losses.CategoricalCrossentropy(from_logits=True),
},
loss_weights=[1.0, 0.2],
)
并在训练过程中按照要求灌入数据,完成训练:
# Dummy input data
title_data = np.random.randint(num_words, size=(1280, 10))
body_data = np.random.randint(num_words, size=(1280, 100))
tags_data = np.random.randint(2, size=(1280, num_tags)).astype("float32")
# Dummy target data
priority_targets = np.random.random(size=(1280, 1))
dept_targets = np.random.randint(2, size=(1280, num_departments))
model.fit(
{"title": title_data, "body": body_data, "tags": tags_data},
{"priority": priority_targets, "department": dept_targets},
epochs=2,
batch_size=32,
)
当然,还有一些诸如共享层、权重提取、重用之类的场景,keras都有实现,详情可以看这个链接:https://tensorflow.google.cn/guide/keras/functional。
自定义层
类似transformer之类的,都是对模型的创新,这时候我们需要自己去写了,只需要按照keras给定的API去写就行:
class CustomDense(layers.Layer):
def __init__(self, units=32):
super(CustomDense, self).__init__()
self.units = units
def build(self, input_shape):
self.w = self.add_weight(
shape=(input_shape[-1], self.units),
initializer="random_normal",
trainable=True,
)
self.b = self.add_weight(
shape=(self.units,), initializer="random_normal", trainable=True
)
def call(self, inputs):
return tf.matmul(inputs, self.w) + self.b
def get_config(self):
return {"units": self.units}
inputs = keras.Input((4,))
outputs = CustomDense(10)(inputs)
model = keras.Model(inputs, outputs)
config = model.get_config()
new_model = keras.Model.from_config(config, custom_objects={"CustomDense": CustomDense})
类里面有几个需要注意的点:
继承layers,并定义好对应的构造函数 __init__
。build中可以进一步对参数进行定义,在第一次使用该层的时候调用该部分代码,在这里创建变量可以使得变量的形状自适应输入的形状,并且给里面的参数进行初始化,可以看到里面有个 initializer
call,在函数式下,执行这一层的时候会调用这个函数进行前向计算。像上面的例子就是做了一个线性变换。 如果是需要支持序列化,则需要用 get_config
或者是from_config
的方式把必要的超参返回出来。
有关自定义新层的方法,详见:https://tensorflow.google.cn/guide/keras/custom_layers_and_models
训练、评估和预测
上面讲的都是模型的建立,现在我们就要谈谈怎么怎么折腾这个模型了。
首先是训练,在进行建模和编译以后,就可以开始训练了:
# 模型编译
model.compile(
optimizer=keras.optimizers.RMSprop(), # Optimizer
# Loss function to minimize
loss=keras.losses.SparseCategoricalCrossentropy(),
# List of metrics to monitor
metrics=[keras.metrics.SparseCategoricalAccuracy()],
)
# 开始训练
history = model.fit(
x_train,
y_train,
batch_size=64,
epochs=2,
# We pass some validation for
# monitoring validation loss and metrics
# at the end of each epoch
validation_data=(x_val, y_val),
)
在进行训练以后就可以进行评估和预测了:
results = model.evaluate(x_test, y_test, batch_size=128)
predictions = model.predict(x_test[:3])
注意,这里的predict其实只是把输出层的预测结果给出,如果是分类问题,可能还要做类似筛选最大之类的后处理。
保存模型
保存模型一共分3种:
保存和加载整个模型 保存架构 保存和加载模型的权重
这3种模式都有很明确的使用场景,例如最后一个权重的,就是在模型预测、迁移学习时使用。
保存和加载整个模型
对于这种模式的保存,是最完整的,按照官方说法,一共覆盖着4种东西:
模型的架构和配置 模型权重 编译信息(compile) 优化器状态,最强的是可以继续训练
API也非常简单:
model.save()
或tf.keras.models.save_model()
tf.keras.models.load_model()
保存架构
保存架构是只保存模型的结构,而不保存权重。
get_config()
和from_config()
tf.keras.models.model_to_json()
和tf.keras.models.model_from_json()
仅保存权重
get_config()
和from_config()
tf.keras.models.model_to_json()
和tf.keras.models.model_from_json()
到此,整个模型的核心流程已经完成,下面我们看一下keras还为我们提供了什么有意思的功能供我们使用。
值得关注的API
经过上面的详细学习,其实大家已经对tensorflow.keras有足够的了解,剩下就是在实践过程中逐步学习成长,但这里面,也有很多指的阅读的API和源码,有兴趣的大家可以好好看看。
layers
这应该是建模的关键,keras目前已经支持几乎所有主流模型,全连接、卷积、池化、RNN等。有兴趣的多去看看,包括里面涉及的变量,都多了解,对后续自己写自定义层有很大好处。
losses
各种损失函数,常见的如BinaryCrossentropy
、MeanSquaredError
,也有一些比较有意思的Hinge
、Poisson
等。
activations
激活函数,可以看看有些啥,炼丹的时候可以都尝试尝试,softmax
、tanh
、relu
,也有elu
之类的新东西。
preprocessing
预处理,这里分成了三个模块,image
、sequence
、text
,很多的预处理工作其实在这里面都有实现。
metrics
评估指标是评价模型好坏的核心,keras里面也内置了大量的已经写好的函数,AUC
、Accuracy
等。
backend
backend里面有大量非常有用的基础函数,熟悉一下,可以大大减轻自己手写代码的压力。来看看几个例子:
argmax
,argmin
,dot
,各种简单算子。gradients
,返回梯度。manual_variable_initialization
,人工特征初始化。
小结
至今仍然感觉,看API、看源码、看文档是对一个工具了解的必经之路,看看博客之类的远远无法真正了解他们,即使是我们已经熟练使用了,回头看看文档,也会发现里面有很多好东西,总之,学无止境吧。