TensorFlow——机器学习编程框架


TensorFlow

TensorFlow是一个机器学习(即亦包括深度学习)的编程框架。

Tensor 张量

张量是tensorflow计算中数据的基本单位,通过.shape获取形状,.dtype获取数值类型,.numpy()获取数值(将张量以numpy数组形式返回)。

变量的域

两种域(scope),名字域(name_scope)和变量域(variable_scope),关于创建和获取变量时变量名解析策略,分别以tf.name_scope('')tf.variable_scope('')
在开启指定scope(即以scope名字实参调用.name_scope('xx')/.variable_scope('xx'))后,对于在其中定义或获取的变量,其名字具有scope名前缀,以/分隔scope名和给定的变量名参数。tf.Variable(name='')在两种域下均受影响,tf.get_variable('')仅在variable_scope下受影响。.name_scope('')/.variable_scope('')可嵌套开启。

import tensorflow as tf

with tf.name_scope('a'):
    v1=tf.Variable(1,name='v1')
    v2=tf.get_variable(name='v2', shape=[1,])
with tf.variable_scope('b'):
    v3=tf.Variable(1, name='v3')
    v4=tf.get_variable(name='v4', shape=[1,])
    
with tf.variable_scope('c'):
    with tf.name_scope('c2'):
        v5=tf.Variable(1, name='v5')

print(v1.name)      # a/v1:0
print(v2.name)      # v2:0
print(v3.name)      # b/v3:0
print(v4.name)      # b/v4:0
print(v5.name)      # c/c1/v5:0

常见方法及功能

(括号内为常用参数,带问号表示可选参数,部分未带问号参数也是可选参数但一般会显式提供):

  • tf.Variable(init_value?,dtype, name?) 创建变量,如果已存在同名变量,则添加后缀'_'。 tf.Variable(otherVar.initialized_value()) 用已存在变量初始化新定义变量
  • tf.get_variable(name) 在开启reuse= REUSE_TRUE| AUTO_REUSE的变量域中意为获取已定义变量,试图获取未定义的变量时将报错,reuse=False时,为创建变量
  • tf.placeholder(dtype, shape) 定义占位符
  • tf.one_hot() 定义one-hot向量
  • tf.variable_scope(name_or_scope) 开启variable_scope
  • tf.name_scope('name_or_scope') 开启name_scope
  • tf.nn.embedding_lookup(params, ids)
  • ops.get_collection() 获取指定图的所有定义变量列表 .get_collection(ops.GraphKeys.GLOBAL_VARIABLES)
  • tf.variables_initializer(var_list=all_variables_list) 为给定变量定义初始化器
  • tf.global_variables_initializer() 定义全局变量初始化器

tf.Variable(name='a')返回对象的属性.name为'a:0'。

tf.constant() 定义常量。

tf.zeros(shape, dtype=整型/浮点型) 定义全0的张量。

tf.ones(shape, dtype=) 定义全1的张量。

tf.concat([...], axis) 拼接张量。

数据类型:

  • tf.int32
  • tf.float32
  • ...

运算:

  • tf.add(a,b) 求和(加)
  • tf. 求差(减)
  • tf.multiply(a,b) 积(乘)
  • tf.square(x) 平方,element-wise。不改变张量形状。
  • tf.div(a,b) 商(除)
  • tf.matmul(A,B) 矩阵乘积
  • tf.pow(x,y) 幂(次方 乘方) 对x中每个元素取幂,如果x,y是结构一样的张量,则是将x中元素作为底,y中对位置元素作为指数取幂。
  • tf.argmax(input, axis) 求张量中的最大元素的索引,沿着轴向http://127.0.0.1:6006 。

    tensorboard --logdir=./tensorboard
    

    Eager Execution 即时运行

    tensorflow eager api: tensorflow具有一个叫做 Eager Execution的特性(与之相对的是Graph Execution),相对于计算图模式而言,eager execution模式下,调用tensorflow下的运算操作会立即计算出结果后返回,而b不像构建计算图,后者要等到被调用.run()才计算。eager api面向简单模型、小规模数据,接口相对简单方便,相关数据可使用python数据结构。
    Tensorflow 2中默认启用即时运行。

    from __future__ import absolute_import, division, print_function
    import tensorflow as tf
    import tensorflow.contrib.eager as tfe
     
    # enabled by default in tensorflow 2.x
    tf.enable_eager_execution()
    
    # tf 2中关闭即时运行模式
    #tf.compat.v1.disable_eager_execution()
     
    x = [[2.]]
    m = tf.matmul(x, x)
    print("hello, {}".format(m))  # => "hello, [[4.]]"
    
    a=tf.constant(5.0)
    b=tf.constant(2.0)
    
    print("*: {}".format(a*b))  # 10.0,直接出计算结果,没有计算图,也不需tf.Session
    
    

    在tensorflow 2.x+ 中计算模式模式是即时运行(Eager Execution),若想以计算图方式执行,可借助@tf.function,被注解的函数将以计算图模式被执行,被注解函数存在限制,其中应尽量使用tf的操作,tf数据结构或numpy数据结构,因部分python特征不受支持(如python的print()应换之以tf.print())。

    tf.TensorArray是tensorflow 2.x+中计算图模式下的动态数组解决方案。

    Tensorflow Keras

    Keras是一套简单易用灵活的深度学习接口框架,tensorflow对其内置支持,相关接口在tf.keras下。

    Keras中模型相关的关键概念是“模型”(Model)和“层”(Layer)。一个神经网络模型由“层”堆叠构成,一个层包括对一个张量的一次变换和一次激活。Tensorflow Keras内置了常见的层,在tf.keras.layers下。“模型”中定义了神经网络的结构,以及训练预测相关的组件操作。

    Keras Model 模型

    模型对于的类为tf.keras.Model,模型实例是可调用对象,对其调用将产生输出(output_y = model(input_X))。

    堆叠层为模型的简单例子:

    from tensorflow import keras
    
    model = keras.Sequential([
        keras.layers.Xxx(),
        keras.layers.Xxx(),
        ])
        
    model.compile(optimizer='',
                    loss='',
                    metrics=['accuracy'])
                    
    model.fit(train_X, train_y, epoch=5)
    
    test_loss, test_acc = model.evaluate(test_X, test_y)
    
    y=model.predict(X)
    

    可自定义模型,其中需要被定义的方法主要有初始化方法__init__(self)以及专门用于被重写的接受输入后产生输出的方法call(self, input)(注意不是__call()__,该方法在tf.keras.Model中已有定义,其中调用了call(),此外还包括其他一些操作)。

    模型构建 Keras Sequential/Functional API

    Keras中提供了将若干子模型/层顺序串连后作为模型的接口 tf.keras.models.Sequential([model1, model2,...]),即Sequential API 。

    Sequential API不能定义多输入/输出等较为复杂的模型,Keras提供了Functional API来构建模型。

    Keras Functional API构建模型的示例代码:

    inputs = tf.keras.Input(shape=(28, 28, 1))
    x = tf.keras.layers.Flatten()(inputs)
    x = tf.keras.layers.Dense(units=100, activation=tf.nn.relu)(x)
    x = tf.keras.layers.Dense(units=10)(x)
    outputs = tf.keras.layers.Softmax()(x)
    model = tf.keras.Model(inputs=inputs, outputs=outputs)
    

    模型训练配置
    compile()方法配置训练过程。
    参数:

    • optimizer 优化器。内置优化器在tf.keras.optimizers.下。
    • loss 损失函数。内置损失函数在tf.keras.losses.下。
    • metrics 评估指标。内置指标在tf.keras.metrics.下。

    模型训练
    fit()方法,训练模型。
    参数:

    • x 训练输入数据。
    • y 训练数据的监督数据。
    • epochs 训练轮数。
    • batch_size 批大小。
    • validation_data 验证集。

    模型测试
    evaluate()方法,评估/测试模型。
    参数:

    • x 测试数据。
    • y 测试数据的监督数据。

    模型预测
    Model.predict(),参数:

    • x

    tf.keras.Model.fit()过程及示例代码:

    # boxed
    import tensorflow as tf
    from tensorflow import keras
    import numpy as np
    
    mnist = keras.datasets.mnist
    
    (train_x, train_y), (test_x, test_y) = mnist.load_data()
    
    train_x = np.expand_dims(train_x.astype(np.float32) / 255.0, axis=-1)
    test_x = np.expand_dims(test_x.astype(np.float32) / 255.0, axis=-1)
    
    # hyper parameters
    num_epochs = 5
    batch_size = 50
    learning_rate = 0.001
    
    model = keras.Sequential([
        keras.layers.Flatten(),
        keras.layers.Dense(100, activation=tf.nn.relu),
        keras.layers.Dense(10, activation=tf.nn.softmax)
    ])
    
    model.compile(optimizer=keras.optimizers.Adam(learning_rate=learning_rate),
                  loss=keras.losses.sparse_categorical_crossentropy,
                  metrics=['accuracy'])
    
    model.fit(train_x, train_y, batch_size=batch_size, epochs=num_epochs)
    
    test_loss, acc = model.evaluate(test_x, test_y)
    
    print('loss: {}, acc: {}'.format(test_loss, acc))
    
    
    #------------------#
    # unbox
    #以下代码来自tensorflow微信公众号
    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, np.shape(self.train_data)[0], batch_size)
            return self.train_data[index, :], self.train_label[index]
    
    class MLP(tf.keras.Model):
        def __init__(self):
            super().__init__()
            self.flatten = tf.keras.layers.Flatten()    # Flatten层将除第一维(batch_size)以外的维度展平
            self.dense1 = tf.keras.layers.Dense(units=100, activation=tf.nn.relu)
            self.dense2 = tf.keras.layers.Dense(units=10)
    
        def call(self, inputs):         # [batch_size, 28, 28, 1]
            x = self.flatten(inputs)    # [batch_size, 784]
            x = self.dense1(x)          # [batch_size, 100]
            x = self.dense2(x)          # [batch_size, 10]
            output = tf.nn.softmax(x)
            return output
    
    num_epochs = 5
    batch_size = 50
    learning_rate = 0.001
    
    model = MLP()
    data_loader = MNISTLoader()
    optimizer = tf.keras.optimizers.Adam(learning_rate=learning_rate)
    
        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))
    
        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())
    

    优化方法 Optimizers

    相关工具方法在tf.keras.optimizers.下。

    常用Adam优化方法,AdamOptimizer(实参可以字符串'adam'代替)。

    优化器的方法apply_gradients(grads_and_vars=[(梯度1,变量1), (梯度2, 变量2)])

    损失函数 Loss Functions

    模块tf.keras.losses.下。

    自定义损失函数,通过继承类tf.keras.losses.Loss重写方法call(y_true, y_pred)来实现。

    sparse_softmax_crossentropy

    交叉熵(cross entropy)损失:
    sparse_categorical_crossentropycategorical_crossentropy都用于计算交叉熵,两者的参数列表皆为(y_true, y_pred),差别在于前者的y_true参数允许传入int类型的标签类别。对于实参预测值y_predint型的真实值y,两个交叉熵函数用以下形式调用得到的结果是一样的:

    loss = tf.keras.losses.sparse_categorical_crossentropy(y_true=y, y_pred=y_pred)
    
    loss = tf.keras.losses.categorical_crossentropy(
        y_true=tf.one_hot(y, depth=tf.shape(y_pred)[-1]),
        y_pred=y_pred
    )
    

    评估指标 Metrics

    进行准确度(accuracy)度量。在模块tf.keras.metrics下。

    自定义评估指标,通过继承类tf.keras.metrics.Metric,定义方法update_state(y_true, y_pred, sample_weight=None)result()来实现。

    两个重要的方法 更新状态update_state(y_true=, y_pred=)、获取结果(准确度)result()

    Estimators

    Estimator是一种高层接口,其封装了训练、评估(测试)、预测、导出等功能。
    tf.estimator.Estimator是estimators的基类。

    tf.keras.estimator.model_to_estimator(keras_model=)是将一个tf.keras.Model转为tf.estimator.Estimator的工具函数。

    Estimator()
    tf.estimator.Estimator()构造函数的参数:

    • model_fn: 模型构建函数,其应返回tf.estimator.EstimatorSpec,其参数列表应是:
      • features 这同train(),evaluate(),predict()input_fn里的features(input_fn返回值的第一个元素)。
      • labelsinput_fn返回值的第二个元素。
      • mode:为tf.estimator.ModeKeys.TRAIN/EVAL/PREDICT,表明是训练/评估/预测。
      • params
      • config
    • model_dir: 保存模型参数、计算图结构、事件、检查点等数据的目录,为None则模型目录将使用config中的参数,若config中也未设置,则使用自动创建的临时目录。默认None
    • config:类型为estimator.RunConfig,运行过程的相关配置。
    • params:模型的超参,将传递给model_fnparams参数,类型为字典,键值的类型应为python基本类型。
    • warm_start_from:热启动时的检查点或SavedModel启动数据位置,类型为路径或tf.estimator.WarmStartSettings

    Estimator.export_saved_model()
    tf.estimator.Estimator.export_saved_model() 将计算图导出为SavedModel。
    参数

    • export_dir_base
    • serving_input_receiver_fn:无参函数,应返回tf.estimator.export.ServingInputReceivertf.estimator.export.TensorServingInputReceiver
    • as_text: 是否将SavedModel proto写为文本格式.
    • checkpoint_path 要导出的检查点的路径。为None(默认值)时表示导出模型目录路径(estimator中的model_dir信息)中的最新检查点。

    Estimator.train()
    参数:

    • input_fn 输入数据的构建函数,其应返回一个tf.data.DataSet其输出为一个元组(features, labels),或返回一个元组(features, labels),二者中的features是一个tf.Tensor或特征名(字符串)到tf.Tensor的字典。
    • hooks
    • steps
    • max_steps
    • saving_listeners

    Estimator.evaluate()
    参数:

    • input_fn 输入数据的构建函数,应返回(features, labels)DataSet,函数抛异常tf.errors.OutOfRangeErrorStopIteration时表示终止评估。
    • steps 评估模型输入数量,为None时评估直到input_fn抛异常。
    • hooks
    • checkpoint_path
    • name

    Estimator.predict()
    参数:

    • input_fn 输入特征的构建函数,该函数抛异常tf.errors.OutOfRangeErrorStopIteration时表示终止预测。
    • predict_keys
    • hooks
    • checkpoint_path
    • yield_single_examples

    Estimator.latest_checkpoint() 查找并返回模型目录中的最新检查点。

    层 Keras Layers

    层对应的类为tf.keras.layers.Layer

    可自定义层,通过继承类tf.keras.layers.Layer,重写方法__init__()build(self, input_shape)call(self, inputs)call()在每次计算时会被调用, build()只会被调用一次,是在层实例第一次执行计算(被调用call())前被调用。

    Dense:一维全连接层

    全连接层tf.keras.layers.Dense是Keras中最基础和常用的层之一,其对输入矩阵进行仿射变换以及激活函数操作。
    其主要参数有:

    • units 输出单元数(即输出维度)。
    • input_shapeinput_dim) 输入形状,在作为输入层(第一层)时需要指定。
    • activation 激活函数,默认无(即单位函数 $f(x)=x$)。
    • use_bias 是否定义偏置单元。默认True
    • kernel_initializer, bias_initializer,输入(称为kernel)和偏置的初始化器。默认tf.glorot_uniform_initializer,欲初始化为0则设置tf.zeros_initializer

    Embedding:嵌入层

    将输入词转为数值向量。这里的词是一个整数代码,比如,该整数定义为词在词典中的索引,而词典的大小即为构造参数中的input_dim
    参数:

    • input_dim 词典大小。
    • output_dim 词嵌入向量空间的维度。
    • ……
    • input_length 序列长度,当序列是固定长度时。若Embedding后接Flatten然后接Dense时,该参数必需,否则无法推断输出层的维度。

    输入尺寸,为 (batch_size, sequence_length) 的 2D 张量。输出尺寸为 (batch_size, sequence_length, output_dim) 的 3D 张量。

    Flatten 扁平化层

    将高阶张量扁平化为一阶张量,即向量,的神经层。常用于从卷积层到全连接层的过渡。扁平化层不改变批大小batch_size,即展平除第一阶外的其他阶(实际最终输出的张量是二阶的而非一阶的)。如mnist中可用Flatten层将形状为[60000, 28, 28, 1]的四阶张量展平为[60000, 784]的二阶张量(第一阶是批大小batch_size,不受影响)。

    GRU 门控循环单元

    GRU(Gated Recurrent Unit),门控循环单元,有两种门,更新门和重置门(LSTM有三种门)。

    LSTM 长短期记忆


    所谓输出'logit',一般指的是未归一化的概率,其后一般作为softmax层的输入。

    tf.data 数据工具

    tf.data.DataSet

    tf.data.Dataset是数据集的抽象。

    Dataset.from_tensor_slices()
    tf.data.Dataset.from_tensor_slices((train_examples, train_labels)) 生成Dataset对象,从numpy数组等数据,该方法适用于数据量较小、能被全部装入内存的场景。(对于数据量大的,考虑使用tf.dta.TFRocrdDataset

    from_tensor_slices()可接受多个张量形成元组的形成作为输入,每个张量(即元组的每个元素)的第一阶的大小需相同(如输入矩阵和标签列向量,第一阶的大小是样本数量,此时输入矩阵行数和标签个数应该相同)。

    DataSet中的常用方法

    • map(map_func, num_parallel_calls=tf.data.experimental.AUTOTUNE) 对其中的元素进行映射,提供参数num_parallel_calls时将使得映射并行化。
    • shuffle(buffer_size) 打乱数据,参数buffer_size是所用缓冲区的大小,打乱程度取决于缓冲区大小。打乱过程是,从缓冲区中随机取出一个元素,其空缺以原数据中下一条补上。特别地,buffer_size=1等于没有打乱数据。另外,大片连续的同标签数据在使用较小的缓冲区大小实参时,其打乱效果也很差,同样很可能是大片连续同标签的。
    • batch(batch_size) 分组为批,将每batch_size个元素打包成一个批(使用tf.stack()进行堆叠)。
    • prefetch(buffer_size=tf.data.experimental.AUTOTUNE) 预取。
    • repeat(), reduce(), take().

    Keras支持Dataset作为输入,Model.fit(),Model.evaluate()时其可接受元素为(样本输入,样本标签)二元组的DataSet对象(此时fit(),evaluate()中的参数y被忽略。

    tf.keras.datasets下有部分常见数据集的下载和操作的相关方法。

    函数Dataset.zip()

    TFRecord 数据集存储格式

    可将数据集以TFRecord格式存储到文件,以便高效处理。

    TFRecord由一系列tf.train.Example序列化后构成,Exmaple则是由若干命名的特征tf.train.Feature构成,命名方式通过字典数据结构实现。

    tf.train.Feature支持以下类型数据:

    • tf.train.BytesList 二进制数据。
    • tf.train.FloatList 浮点型数据。
    • tf.train.Int64List 整型数据(int32/int64)。

    创建数据时提供给构造函数tf.train.BytesList/FloatList/Int64List(value=[])的参数value的值应是数组,单个元素数据应该裹以数组。

    TFRcord格式文件的生成由tf.io.TFRecordWriter写器来完成,写器的.write()方法接受的是Exmaple序列化为字符串的值即write(Exmaple.SerializeToString())

    读取以tf.data.TFRecordDataset(filenames)读器来完成,然后通过方法map()来还原数据集,其参数是一个反序列化函数,一般会借助tf.io.parse_single_example()

    示例代码:

    import tensorflow as tf
    
    filename='a.tfrecord'
    #写
    with tf.io.TFRecordWriter(filename) as writer:
        for (img_file, label) in zip(img_files, labels):
            img=open(img_file, 'rb').read()
            feats={
                'image': tf.train.Feature(bytes_list=tf.train.BytesList(value=[img])),
                'label': tf.train.Feature(int64_list=tf.train.Int64List(value=[label])
            }
            exmaple=tf.train.Example(features=tf.train.Features(feature=feats))
            writer.write(exmaple.SerializeToString())
            
    #读
    raw_ds = tf.data.TFRecordDataset(tfrecord_file)
    feat_desc= {
        'image': tf.io.FixedLenFeature([], tf.string),
        'label': tf.io.FixedLenFeature([], tf.int64)
    }
    def parse_one(exmaple_str):
        feat_dict = tf.io.parse_single_example(exmaple_str, feat_desc)
        img=tf.io.decode_jpeg(feat_dict['image'])
        label=feat_dict['label']
        return img,label
    
    ds = raw_ds.map(parse_one)
    

    模型保存与加载 Model Saving/Export & Loading

    模型可被保存为文件,之后在其他程序中被加载出来使用。

    模型可在训练过程中随时被保存(检查点checkpoint机制),之后被加载出来继续训练。

    模型的保存与加载时涉及两方面的数据,模型的结构和权重。模型的结构即是构成模型的网络层结构、各层类型、输入输出参数形状、激活函数、优化器等等;模型权重即是各层权重张量的数值。模型的结构与权重可分开保存到各自文件,也可保存到一个文件。加载同样。

    Keras Model Saving & Loading

    整个模型(结构+权重)

    • 保存tf.keras.Model.save(),实例方法。
      参数:

      • filepath 文件路径
      • save_format 以指定格式保存,为'h5''tf',前者是Keras H5格式,后者是tensorflow SavedModel格式。默认由文件名推断而来,当其扩展名是.h5.keras时格式为'h5',否则为'tf'。
    • 加载tf.keras.models.load_model(),函数。参数为模型文件路径,不需要提供类似保存时的save_format='h5|tf'参数。

    仅模型结构

    • 保存tf.keras.Model的实例方法.to_json(file).to_yml(file),分别保存为json和yml格式文件。
    • 加载tf.keras.models.model_from_json(),model_from_yml()

    仅权重

    • 保存tf.keras.Model.save_weights()
      参数:
      • filepath 文件路径
      • save_format 以指定格式保存,为'h5''tf',后者为tensorflow checkpoint格式。默认由文件名推断而来,当其扩展名为.h5时为'h5',否则为'tf'。
    • 加载tf.keras.Model.load_weights(),实例方法。参数为文件路径,不需要类似保存时的数据格式参数save_format=。

    示例代码(利用keras的callback机制和类ModelCheckpoint进行断点保存与恢复)

    
    checkpoint_path = "training_1/cp.ckpt"
    #checkpoint_path = "training/ckp_{epoch}"       #自动捕获变量epoch的值,以做替换
    
    
    checkpoint_dir = os.path.dirname(checkpoint_path)
    
    #配置,为保存行为
    cp_callback = tf.keras.callbacks.ModelCheckpoint(checkpoint_path,
                                                     save_weights_only=True,    #仅保存权重(否则保存包括了模型结构的完整模型数据)
                                                     verbose=1)
    
    model.fit(...,
            callbacks=[cp_callback])    #加入callback参数将使得保存操作一直伴随训练进行
    
    #手动保存权重
    model.save_weights('model_weights.h5')
    
    #加载权重
    m= #通过代码构建出同样的模型结构
    m.load_weights(checkpoint_path)
    
    #从检查点系列文件中查找最新者
    ckpt_path = tf.train.latest_checkpoint(checkpoint_dir)
    #加载
    model = tf.keras.models.load_model(ckpt_path)
    
    
    #保存整个模型,不仅包括权重,还有模型及其配置、优化器等,以HDF5格式文件保存
    model = #通过代码构建模型结构
    #训练
    model.fit(train_x, train_y)
    #训练好后准备保存
    #保存
    model.save('my_model.h5')
    
    model.save('mymodel', save_format='tf') #以tf `SavedModel`格式保存
    model.save('mymodel', save_format='h5') #显式指定以keras h5格式保存
    
    #加载模型
    model = tf.keras.models.load_model('my_model.h5')
    #load_model还可加载SavedModel格式的模型,*不需要*提供类似保存时的参数save_format='tf'
    
    #保存模型结构,可为json格式或yml格式
    model.to_json('my_model.json')
    model.save_weights('model_weights.h5')
    
    #加载
    #从文件加载模型结构
    model = tf.keras.models.model_from_json('my_model.json')
    #加载权重
    modle.load_weights('model_weights.h5')
    

    tf.train.Checkpoint

    Tensorflow有检查点机制以保存和恢复数据(模型的权重、变量、优化器等),但不能保存模型的结构。适用于知道源码,或其他能够构建出原模型结构的场景。

    tf.train.Checkpoint(**kwargs)的参数时键值对,键是任意的,但恢复时需要用到键名,值可以是模型(但模型的结构不会被保存)、变量、优化器等。

    save(file_prefix)保存数据为检查点,参数$file_prefix是保存路径前缀(目录及文件名前缀),保存时将会生成系列文件checkpoint, $file_prefix-.index, $file_prefix-.data-00000-of-00001,其中的为数字,是自动生成的序号,时间上越新保存的检查点其序号越大。save()将返回系列文件的路径前缀,形如$file_prefix-,该值用于恢复方法restore()的实参。

    restore()恢复数据。参数是save()返回的路径前缀,形如"$file_prefix-",其中是数字序号。需要注意的是,这个字符串实参并非完全作为文件路径来被理解,如保存到目录"out/model.ckpt",假设save()返回"out/model.ckpt-1",则restore()的实参应为"out/model.ckpt-1",不可以是"out//model.ckpt",尽管在作为文件路径来理解时后者与前者指向的路径是一致的。而实参"out/../out/model.ckpt-1"又是可以的。

    tf.train.latest_checkpoint():检测给定目录中最新的检查点路径(用于restore())。参数是目录的路径(不是文件路径的前缀)。

    tf.train.CheckpointManager:限制最多保留的检查点(系列)文件、自定义编号。使用方法,保存模型不再直接调tf.train.Checkpoint.save(),而是通过CheckpointManager.save()来实现。

    tf.train.CheckpointManager()参数:

    • checkpoint 受管的tf.train.Checkpoint实例。
    • directory 保存目录。
    • checkpoint_name 系列文件的共有文件名前缀(不含编号)。
    • max_to_keep 检查点保留的最多个数。

    CheckpointManager.save() 参数:

    • checkpoint_number 编号,为None(默认)时表示自动编号。

    保存的示例的代码:

    model1 = new MyModel()
    model1.fit(...)     #训练模型
    #经过一系列处理,待保存的模型、变量具有数据
    #然后保存
    ckpt = tf.train.CheckPoint(my_model = model1, my_var1 = var1, my_optimizer100 = optimizer1)
    
    ckpt.save("out/model.ckpt")     #返回值可能是 out/model.ckpt-1
    
    #或CheckpointManager
    ckpt = tf.train.Checkpoint(my_model = model1, ...)
    manager = tf.train.CheckpointManager(ckpt, directory='out', checkpoint_name='model.ckpt', max_to_keep=3)
    for batch_index in range(num_batchs):
        ... model1 ...
        if ...: #隔一段时间保存一次
            manager.save(checkpoint_number=batch_index)
    

    恢复的示例代码:

    model = new MyModel()
    #恢复
    ckpt = tf.train.CheckPoint(my_model = model)    #键名与保存时一致
    ckpt.restore('out/model.ckpt-1')
    #或检测最新检查点的文件名
    ckpt.restore(tf.train.latest_checkpoint('out'))
    

    Tensorflow SavedModel

    SavedModel是tensorflow中的一种模型完整存储的格式(及相关工具),其主要应用场景是在tensorflow不同语言接口间(python,java,c++)、或tensorflow不同平台间(tensorflow lite, tensorflow serving, tensorflow hub)使用和部署tensorflow程序/模型。

    SavedModel格式存储(python)

    • 利用实例方法tf.keras.Model.save(, save_format='tf')来保存一个keras模型为SavedModel格式。
    • 或利用tf.saved_model.save(dir, model), 或tf.saved_model.simple_save(), 或tf.saved_model.builder.SavedModelBuilder
    • 对Esatimator,利用实例方法tf.Estimator.export_saved_model()来保存。

    SavedModel包含若干关键的信息,所谓"signature",即命名函数,以保存计算图。

    对于tf.keras.Model类型的模型可利用上述两个方法很简便地进行存储为SavedModel,但自定义模型则需要做另外的工作。

    自定义模型,其中以@tf.function装饰的函数可被保存到SavedModel。

    加载SavedModel(Python):利用tf.saved_model.loader.load(), tf.saved_model.load()

    存储和加载的示例代码(Python):

    model = tf.keras.Model(...)
    #保存为SavedModel
    model.save('my-dir', save_format='tf')
    #或通过tf.saved_model.save()
    tf.saved_model.save(model, 'my-dir2')
    #或通过tf.saved_model.simple_save()
    tf.saved_model.simple_save()
    #或通过tf.saved_model.builder.SavedModelBuilder
    builder = tf.saved_model.builder.SavedModelBuilder("my-dir4")
    signature = predict_signature_def(inputs={'Input': x},
                                      outputs={'Output': y})
    builder.add_meta_graph_and_variables(sess=sess,
                                         tags=['my-tag'],
                                         signature_def_map={'predict': signature})
    builder.save()
    
    #加载一个SavedModel
    m = tf.saved_model.loader.load(...)
    m = tf.saved_model.load(...)
    
    #自定义模型保存为SavedModel
    import tensorflow as tf
    
    class MyMo(tf.Module):      #需要继承tf.Module,因被存储对象需要是Trackable
        def __init__(self):
            super(MyMo, self).__init__()
            self.k = tf.Variable(2.0)
    
        @tf.function(input_signature=[tf.TensorSpec([], tf.float32, name='in_x')])
        def __call__(self, x):
            return x * self.k
    
        @tf.function(input_signature=[tf.TensorSpec([], tf.float32)])   //signature参数值必须是list或tuple
        def set_k(self, new_k):
            self.k.assign(new_k)
    
    
    m = MyMo()
    print(m(tf.constant(0.0)).numpy())
    print(m(tf.constant(2.0)).numpy())
    tf.saved_model.save(m, 'out/m')
    
    #加载
    import tensorflow as tf
    
    m = tf.saved_model.load('out/m')
    print(m(tf.constant(3.0)).numpy())
    m.set_k(tf.constant(2.0))
    print(m(tf.constant(3.0)).numpy())
    

    利用saved_model_cli查看已存储的SavedModel的结构:

    saved_model_cli show --dir out\m\4 --all
    

    输出:

    MetaGraphDef with tag-set: 'serve' contains the following SignatureDefs:
    
    signature_def['__saved_model_init_op']:
      The given SavedModel SignatureDef contains the following input(s):
      The given SavedModel SignatureDef contains the following output(s):
        outputs['__saved_model_init_op'] tensor_info:
            dtype: DT_INVALID
            shape: unknown_rank
            name: NoOp
      Method name is:
    
    signature_def['serving_default']:
      The given SavedModel SignatureDef contains the following input(s):
        inputs['in_x'] tensor_info:
            dtype: DT_FLOAT
            shape: ()
            name: serving_default_in_x:0
      The given SavedModel SignatureDef contains the following output(s):
        outputs['output_0'] tensor_info:
            dtype: DT_FLOAT
            shape: ()
            name: StatefulPartitionedCall:0
      Method name is: tensorflow/serving/predict
    

    加载SavedModel(Java):利用org.tensorflow.SavedModelBundle

    def main() {
        val m = SavedModelBundle.loader("out/m")
                    .withTags("serve")      //默认tag是'serve'
                    .load();
        //或
        val m = SavedModelBundle.load("out/m", "serve");
        Tensor xFeed = Tensor.create(2.0F);     //构建模型输入
        val output = m.session()
                          .runner()
                          .feed("serving_default_in_x:0", xFeed)    //输入的名字可通过命令行工具saved_model_cli查看SavedModel而来
                          .fetch("StatefulPartitionedCall:0")
                          .run()
                          .get(0);
                         
        println(out.floatValue())
    }
    

    ServingInputReceiver

    tf.estimator.export.ServingInputReceiver是导出为SavedModel的函数中的参数serving_input_receiver_fn的返回类型。
    ServingInputReceiver的返回类型应是features:TensorSparseTensor或字典string/intTensor/SparseTensor,非字典数据将被包装为字典,其只有一个键feature,即{'feature': tenso}ServingInputReceiver只能用于能够允许这种字典结构作为输入的模型。对于仅接受张量而非字典的为输入的模型,导出模型时相关参数应该使用类型TensorServingInputReceiver,其不会进行这种字典包装。

    __new__ 方法

    @staticmethod
    __new__(
        cls,
        features,
        receiver_tensors,
        receiver_tensors_alternatives=None
    )
    

    TensorServingInputReceiver

    tf.estimator.export.TensorServingInputReceiver,类似ServingInputReceiver,但后者返回的只会是字典即使提供的只是张量都会被自动包装为字典,这要求模型必须能处理类型为字典的输入,而对于那些只接受类型为张量的输入的模型,此时不能使用ServingInputReceiver,而应用TensorServingInputReceiver,其则不会自动进行包装,返回用户提供的值。

    tf.estimator.export.build_raw_serving_input_receiver_fn()
    参数

    • features 字符串到张量的字典
    • default_batch_size 批次大小,默认None

    tf.estimator.export.build_parsing_serving_input_receiver_fn()
    参数

    • feature_spec 字符串到VarLenFeature/FixedLenFeature的字典
    • default_batch_size

    示例代码

    简单例子 $y=kx+b$运算:

    import tensorflow as tf     #一般将tensorflow重命名为tf
    
    #定义常量并初始化
    pi = tf.constant(3.14159, name='pi')     #定义名为pi值为3.14159的常量
    
    #定义变量
    x = tf.Variable(0.0, name='x', dtype=tf.float32)  #定义变量,可提供初始化值、可指定数据类型(一般都显式指定),默认数据类型由初始化值推断而来
    k = tf.Variable(1.0,name='k',dtype=tf.float32)
    
    #定义运算(计算图computation graph) y=kx+π
    kx = tf.multiply(k,x, name='kx')
    y = tf.add(kx,pi,name='y')
    #tensorflow中有'+'、'-'、'*'、'÷'、等运算符,但由于存在重载、覆盖,故一般显示使用对应英文名方法来定义运算
    
    #创建初始化器
    init_op = tf.global_variables_initializer()
    
    with tf.Session() as sess:
        sess.run(init_op)   #执行初始化
        output=sess.run(y)  #执行计算
        print('value: ', output)
        
    
    

    tensorflow placeholder例子:

    import tensorflow as tf
    import numpy as np
    
    #定义k,pi,y,初始化器
    pi = tf.constant(3.14159, name='pi')
    x = tf.placeholder(tf.float32, [None, 1], name='x')
    k = tf.Variable(1.0, name='k', dtype=tf.float32)
    kx = tf.multiply(k, x, name='kx')
    y = tf.add(kx, pi, name='y')
    
    #定义占位符(变量符号,计算时才给值)
    x=tf.placeholder(tf.float32,[None,1], name='x')     # 维度:[None,1]表示第一轴向长度暂不可知,由给的值或者计算式确定
    
    #占位符需在计算时“填值”(feed)
    with tf.Session() as sess:
        tf.run(init_op)
        v=tf.run(y, feed_dict={x: np.arange(0, 10)[:, np.newaxis]})
        print('value: ', v)     #将输出列向量,因给变量符号填值为列向量
    
    

    mnist例子(单层神经网络,交叉熵损失,softmax输出):

    import tensorflow as tf
    #超参
    learning_rate = 0.5
    epochs = 10
    batch_size = 100
    
    n_x = 28 * 28  # number of features 样本特征量
    k = 10  # (n_y) number of classes 预测数字为0~9中的一个,用one-hot向量表示
    n_h = 300  # number of hidden units
    
    X = tf.placeholder(tf.float32, [None, n_x])  # 模型输入。图片像素28x28,
    Y = tf.placeholder(tf.float32, [None, k])  # 监督值。单个样本的标签表示为10维的one-hot向量
    W1 = tf.Variable(tf.random_normal([n_x, n_h]), name='W1')  # 以正态分布随机初始化
    b1 = tf.Variable(tf.random_normal([n_h], stddev=0.03), name='b1')
    W2 = tf.Variable(tf.random_normal([n_h, k]))
    b2 = tf.Variable(tf.random_normal([k], stddev=0.03))
    
    Z1 = tf.add(tf.matmul(X, W1), b1)
    A1 = tf.nn.relu(Z1)
    
    Z2 = tf.add(tf.matmul(A1, W2), b2)
    Y_hat = tf.nn.softmax(Z2)  # 模型预测值
    Y_hat_clipped = tf.clip_by_value(Y_hat, 1e-10, 1 - 1e-7)  # 修剪预测值避免数值问题
    # 损失函数。tf.sum是沿着轴向axis=1的,是因数据排列时轴向axis=1代表特征,
    # .sum()结果是样本量为长度的一个向量,对此求均值(.mean())
    J = -tf.reduce_mean(tf.reduce_sum(Y * tf.log(Y_hat_clipped) + (1 - Y) * tf.log(1 - Y_hat_clipped), axis=1))
    
    optimizer = tf.train.GradientDescentOptimizer(learning_rate=learning_rate).minimize(J)
    
    init_op = tf.global_variables_initializer()
    
    pred = tf.equal(tf.argmax(Y, 1), tf.argmax(Y_hat, 1))  # m x 1 matrix of True|False
    accuracy = tf.reduce_mean(tf.cast(pred, tf.float32))
    
    # loading dataset
    
    from tensorflow.examples.tutorials.mnist import input_data
    
    mnist = input_data.read_data_sets(mnistdatacode_dir + '/data', one_hot=True, reshape=True)
    
    # 训练
    with tf.Session() as sess:
        sess.run(init_op)
        n_batch = int(len(mnist.train.labels) / batch_size)
        for epoch in range(epochs):
            avg_loss = 0.0
            for i in range(n_batch):
                X_batch, Y_batch = mnist.train.next_batch(batch_size)
                _, c = sess.run([optimizer, J], feed_dict={X: X_batch, Y: Y_batch})
                avg_loss += c / n_batch
            print("epoch: {}, loss = {:.2f}%".format((epoch + 1), avg_loss * 100))
        acc = sess.run(accuracy, feed_dict={X: mnist.test.images, Y: mnist.test.labels})
        print()
        print("acc: ", acc)
    
    
    #翻车了。输出的epoch loss达3000%+,acc仅一二十个百分点
    

    猫狗分类示例:

    #例子思路参考自tensorflow中文微信公众号
    import tensorflow as tf
    import os
    
    # URL to download dataset
    #https://www.floydhub.com/fastai/datasets/cats-vs-dogs/2/ train/(531M), valid/(44M)
    data_dir='D:/Downloads/dogs-vs-cats/'
    batch_size=32
    num_epochs=3
    learning_rate=1E-3
    
    def _decode_resize(file,label):
        img_bin=tf.io.read_file(file)
        img=tf.image.decode_jpeg(img_bin)
        img=tf.image.resize(img, [256,256])/255.0
        return img, label
    
    def load_ds(dir):
        dog_dir=os.path.join(dir,'dogs')
        cat_dir=os.path.join(dir, 'cats')
        dg=tf.constant([os.path.join(dir,'dogs',fn) for fn in os.listdir(dog_dir)])
        ct=tf.constant([os.path.join(dir,'cats',fn) for fn in os.listdir(cat_dir)])
        dx=tf.concat([ct, dg], axis=0)
        dy=tf.concat([tf.zeros(ct.shape,dtype=tf.int32),
                     tf.ones(dg.shape, dtype=tf.int32)], axis=0)
        ds=tf.data.Dataset.from_tensor_slices((dx, dy))
        ds=ds.shuffle(buffer_size=10000)
        ds=ds.map(map_func=_decode_resize, num_parallel_calls=tf.data.experimental.AUTOTUNE)
        ds=ds.batch(batch_size)
        ds=ds.prefetch(buffer_size=tf.data.experimental.AUTOTUNE)
        return ds
    
    model=tf.keras.Sequential([
        tf.keras.layers.Conv2D(32, 3, activation='relu', input_shape=(256,256, 3)),
        tf.keras.layers.MaxPooling2D(),
        tf.keras.layers.Conv2D(32, 5, activation='relu'),
        tf.keras.layers.MaxPooling2D(),
        tf.keras.layers.Flatten(),
        tf.keras.layers.Dense(units=32, activation='relu'),
        tf.keras.layers.Dense(units=2, activation='softmax')
    ])
    
    print('compiling')
    model.compile(
    optimizer=tf.keras.optimizers.Adam(learning_rate=learning_rate),
        loss=tf.keras.losses.sparse_categorical_crossentropy,
        metrics=[tf.keras.metrics.sparse_categorical_accuracy]
    )
    print('loading data')
    tr_ds=load_ds(os.path.join(data_dir, 'train'))
    print('training')
    model.fit(tr_ds,epochs=num_epochs)
    
    print('loading test data')
    test_ds=load_ds(os.path.join(data_dir, 'valid'))
    print('evaluating')
    print(model.metrics_names)
    print(model.evaluate(test_ds))
    

    #测试是否可用GPU,当安装的是CPU版tensorflow(即未安装tensorflow-gpu)时会返回False
    tf.test.is_gpu_avaiable()
    
    # tf库是否编译入了CUDA支持(CPU版本时返回False, Nvidia GPU版本时应返回True)
    tf.test.is_built_with_cuda()
    
    tf.__version__  # tf库的版本
    

    安装

    安装tensorflow(CPU版):

    #激活一个conda环境
    conda env list
    # conda activate 
    source activate 
    
    #安装tensorflow
    pip install tensorflow
    
    #安装tensorflow 2
    #要求pip >= 19.0
    pip --version   #查看pip版本
    pip install --upgrade pip   #升级pip
    #pip install tensorflow==       #查看tensorflow包的可用版本
    pip install tensorflow
    #pip install tensorflow==2.0.0
    

    GPU加速:
    需要

    • Nvidia显卡及其驱动
    • CUDA工具集
    • cuDNN
    • 设置PATH环境变量
    • tensorflow(GPU版)

    cuDNN的版本号是自身独立的,没有对应CUDA版本(如cudnn7.6适配的是cuda10),cudatoolkit的大版本号一般对应cuda大版本号(cudatoolkit10对应cuda10)。

    GPU版tensorflow: tensorflow-gpu

    #不必安装依赖包'tensorflow'(即CPU版),直接安装包'tensorflow-gpu'即可
    pip install tensorflow-gpu
    
    #运行
    CUDA_VISIBLE_DEVICES=0 python FILE
    #语句CUDA_VISIBLE_DEVICES=0 是设置临时环境变量使得cuda环境使用GPU设备0
    #可设置使用多个GPU设备 CUDA_VISIBLE_DEVICES=0-3 (设备号在0到3的设备,共4个) CUDA_VISIBLE_DEVICES=0,2 (设备0和2)
    

    错误:CUDA driver version is insufficient for CUDA runtime version。

    python库的cudatoolkit版本和系统CUDA环境版本不一致。升级CUDA环境,或降级ccudatoolkit的python库。
    降级:

    conda uninstall cudatoolkit     #会同时卸载tensorflow
    conda install cudatoolkit=9.2
    conda install tensorflow-gpu
    

    如果同时安装了CPU版和GPU版,默认运行GPU版。如果需要运行cpu版,可在使用with tf.device(''):指定要使用的设备。

    测试GPU是否可用:

    tf.test.is_gpu_available()
    #同接口
    tf.compat.v2.test.is_gpu_available()
    tf.compat.v1.test.is_gpu_available()
    

    Tensorflow Java API

    依赖库的(maven)坐标org.tensorflow:libtensorflow,需要GPU支持的需额外添加依赖org.tensorflow:libtensorflow_jni_gpu

    在maven pom.xml中添加TF依赖:

    
        org.tensorflow
        tensorflow
        1.15.0
    
    
        org.tensorflow
        libtensorflow_jni_gpu
        1.15.0
    
    

    Tensor代表张量,其内存在堆外(off-heap memory),一定要记得释放。

    SavedModelBundle代表TF SavedModel,使用完毕后记得释放。其.session()始终返回同一个会话,即每次.session()返回值都一样,不要调用其Session.close()除非后续一直不会再使用,否则再次跑模型时将异常,提示会话已关闭,建议释放资源通过SavedModelBundle.close()而非Session.close(),前者除了释放Session还会释放Graph,而后者仅释放了Session自己。

    Scala代码:

    val labelList:List[Label] = List("pos", "neg")
    val int2label = labelList.view.zipWithIndex.map(_.swap).toMap
    
    //加载模型
    val tfModel = SavedModelBundle.load("/path/to/model", "serve")
    
    def predict(texts: scala.collection.Seq[InputTextExample]): Seq[Label] = {
        import scala.language.existentials
        val (inputIds_arr, inputMask_arr, segmentIds_arr) = text2input_arr(texts)
        Using.resource(     //自动释放
          Using.resources(
            Tensor.create(inputIds_arr),    //创建Tensor使用了堆外内存,需释放,这里采用自动释放
            Tensor.create(inputMask_arr),
            Tensor.create(segmentIds_arr)
          ) { (inputIds, inputMask, segmentIds) =>
            tfModel.session
              .runner()
              .feed("input_ids", inputIds)
              .feed("input_mask", inputMask)
              .feed("segment_ids", segmentIds)
              .fetch("loss/Softmax")
              .run()
              .get(0)
          }
        ) { r =>
    
          assert(r.shape()(0) == texts.length && r.shape()(1) == labelList.length)
    
          val probs = Array.ofDim[Float](texts.length, labelList.length)
          r.copyTo(probs)
          probs.view.map(p => argmax(p)).map(int2label).toSeq
        }
      }
    
    def close() {
        tfModel.close()
    }
    
    def text2input_arr(texts: scala.collection.Seq[InputTextExample]): (Array[Array[Int]], Array[Array[Int]], Array[Array[Int]]) = {...}
    

    Java代码示例:

    public void init() {
        tfModel = SavedModelBundle.load("/path/to/model", "serve")
    }
    
    public java.util.List

    注意版本TF Java API 1.14.0(即org.tensorflow:libtensorflow:1.14.0)有明显低级漏洞(BUG),其根本无法初始化TF,因无法加载TF Jar自己解压出的C++库(至少在linux平台,其他平台暂未测试),提示错误

    "java.lang.UnsatisfiedLinkError: /tmp/tensorflow_native_libraries-157xxxxxxxx-0/libtensorflow_jni.so: libtensorflow_framework.so.1: cannot open shared object file: No such file or directory"。

    其中目录"/tmp/tensorflow_xxx"是一个临时目录,故因运行而异。

    FAQ

    ERROR: gpu ... getattr...
    cudnn、cudatoolkit等库是否安装?版本是否符合?是否有GPU硬件?

    windows环境下错误信息:access viloation
    如果存在读取文件/目录情况,有可能是因为不能找到文件,即路径错误(如google bert代码中checkpoint路径错误时)。这种问题也可能因某个库的版本间接导致。