image.png

1.低阶API

TensorFlow的低阶API主要包括张量操作,计算图和自动微分。
在低阶API层次上,可以把TensorFlow当做一个增强版的numpy来使用。

1.1张量的结构操作

张量创建,索引切片,维度变换,合并分割等
(张量的各个元素在内存中是线性存储的,其一般规律是,同一层级中的相邻元素的物理地址也相邻。)

  1. ###索引切片:
  2. #对于规则切片,和numpy的操作几乎一致。
  3. #对于不规则的切片提取,可以使用tf.gather,tf.gather_nd,tf.boolean_mask。
  4. ###通过修改张量的部分元素值得到新的张量,可以使用tf.where和tf.scatter_nd
  5. ###维度变换相关函数主要有
  6. tf.reshape(改变形状时,可以增加/减少维度),
  7. tf.squeeze(减少维度,squeeze:压缩/挤压),
  8. tf.expand_dims(增加维度),
  9. tf.transpose.
  10. tf.transpose会改变张量元素的存储顺序。tf.squeeze/reshape不会改变张量元素的存储顺序。)
  11. ###合并分割
  12. tf.concattf.stack方法对多个张量进行合并;
  13. tf.concat是连接,不会增加维度,而tf.stack是堆叠,会增加维度)
  14. tf.split把一个张量分割成多个张量。

1.2张量的数学运算

标量运算,向量运算,矩阵运算,张量运算的广播机制等。

  1. #标量运算符的特点是对张量实施逐元素运算。
  2. #向量运算符只在一个特定轴上运算,将一个向量映射到一个标量或者另外一个向量。
  3. 许多向量运算符都以reduce开头。(加、减、乘、除、最大、最小、排序等)
  4. #矩阵运算包括:
  5. 矩阵乘法,矩阵转置,矩阵逆,矩阵求迹,矩阵范数,矩阵行列式,矩阵求特征值,矩阵分解等运算。
  6. #广播机制:
  7. TensorFlow的广播规则和numpy是一样的:
  8. 1、如果张量的维度不同,将维度较小的张量进行扩展,直到两个张量的维度都一样。
  9. 2、如果两个张量在某个维度上的长度是相同的,或者其中一个张量在该维度上的长度为1
  10. 那么我们就说这两个张量在该维度上是相容的。
  11. 3、如果两个张量在所有维度上都是相容的,它们就能使用广播。
  12. 4、广播之后,每个维度的长度将取两个张量在该维度长度的较大值。
  13. 5、在任何一个维度上,如果一个张量的长度为1,另一个张量长度大于1
  14. 那么在该维度上,就好像是对第一个张量进行了复制。
  15. tf.broadcast_to 以显式的方式按照广播机制扩展张量的维度。

1.3计算图:Autograph

Autograph机制可以将动态图转换成静态计算图,兼收执行效率和编码效率之利。
Autograph的使用规范

  • 被@tf.function修饰的函数应尽可能使用TensorFlow中的函数而不是Python中的其他函数。例如使用tf.print而不是print,使用tf.range而不是range,使用tf.constant(True)而不是True。

(普通Python函数是无法嵌入到静态计算图中的,所以在计算图构建好之后再次调用的时候,这些Python函数并没有被计算,而TensorFlow中的函数则可以嵌入到计算图中。)

  • 避免在@tf.function修饰的函数内部定义tf.Variable。

(如果定义了,会直接报错。如果不在@tf.function修饰的函数内部定义tf.Variable,可以将相关的tf.Variable创建放在类的初始化方法中,将函数的逻辑放在其他方法中。TensorFlow提供了一个基类tf.Module来实现这个想法。利用tf.Module提供的封装,再结合TensoFlow丰富的低阶API,能基于TensorFlow开发任意机器学习模型(而非仅仅是神经网络模型),并实现跨平台部署使用。)

  • 被@tf.function修饰的函数不可修改该函数外部的Python列表或字典等数据结构变量。

(静态计算图是被编译成C++代码在TensorFlow内核中执行的。Python中的列表和字典等数据结构变量是无法嵌入到计算图中,它们仅仅能够在创建计算图时被读取。)
Autograph的机制原理
当第一次调用被@tf.function修饰的函数时,后台:第一,先创建计算图(创建一个静态计算图,跟踪执行一遍函数体中的Python代码,确定各个变量的Tensor类型,并根据执行顺序将算子添加到计算图中)。第二,执行计算图。

1.4自动微分机制

Tensorflow一般使用梯度磁带tf.GradientTape来记录正向运算过程,然后反播磁带自动得到梯度值。
这种利用tf.GradientTape求微分的方法叫做Tensorflow的自动微分机制。

  • 求导

    1. dy_dx = tape.gradient(y,x)
  • 优化:

    1. optimizer = tf.keras.optimizers.SGD(learning_rate=0.01)
    2. optimizer.apply_gradients(grads_and_vars=[(dy_dx,x)])
    3. #OR
    4. optimizer.minimize(f,[x])

    2.中阶API

    2.1数据管道(tf.data)

    如果需要训练的数据大小不大,例如不到1G,那么可以直接全部读入内存中进行训练,这样一般效率最高。但如果需要训练的数据很大,例如超过10G,无法一次载入内存,那么通常需要在训练的过程中分批逐渐读入。使用 tf.data API 可以构建数据输入管道,轻松处理大量的数据,不同的数据格式,以及不同的数据转换。

    2.1.1数据转换功能

    Dataset数据结构应用非常灵活,因为它本质上是一个Sequece序列,其每个元素可以是各种类型,例如可以是张量,列表,字典,也可以是Dataset。Dataset包含了很多数据转换功能:map/reduce/flat_map/interleave/filter/zip/concatenate/batch/padded_batch/window/shuffle/repeat/shard/take。

    2.1.2构建高效的数据管道

    模型训练的耗时主要来自于两个部分,一部分来自数据准备,另一部分来自参数迭代。参数迭代过程的耗时通常依赖于GPU来提升。而数据准备过程的耗时则可以通过构建高效的数据管道进行提升。
    (当参数迭代过程成为训练时间的主要瓶颈时,我们通常的方法是应用GPU或者Google的TPU来进行加速,例如:多GPU训练模型等)

    1. 以下是一些构建高效数据管道的建议。
    2. 1,使用 prefetch 方法让数据准备和参数迭代两个过程相互【并行】。
    3. 2,使用 interleave 方法可以让数据读取过程【多进程】执行,并将不同来源数据夹在一起。
    4. 3,使用 map 时设置num_parallel_calls 让数据转换过程【多进程】执行。
    5. 4,使用 cache 方法让数据在第一个epoch后【缓存】到内存中,仅限于数据集不大的情形。
    6. 5,使用 map转换时,先【批量】batch,后map, 然后采用向量化的转换方法对每个batch进行转换。
  • tfrecords文件方式构建数据管道

tfrecords文件的优点是压缩后文件较小,便于网络传播,加载速度较快。做法是对样本构建tf.Example后压缩成字符串写到tfrecords文件,读取后再解析成tf.Example。

  • 从Numpy array、Pandas DataFrame构建数据管道

    1. tf.data.Dataset.from_tensor_slices
  • 从Python generator构建数据管道

    1. tf.data.Dataset.from_generator
  • 从csv文件、文本文件、文件路径构建数据管道

    1. # CSV文件
    2. tf.data.experimental.make_csv_dataset
    3. # 文本文件
    4. tf.data.TextLineDataset
    5. # 文件路径
    6. tf.data.Dataset.list_files

2.2特征列(tf.feature_column)

特征列通常用于对结构化数据实施特征工程时候使用,图像或者文本数据一般不会用到特征列。
使用特征列可以将类别特征转换为one-hot编码特征,将连续特征构建分桶特征,以及对多个特征生成交叉特征等等。

2.3激活函数(tf.nn)

  1. model = models.Sequential()
  2. #通过activation参数指定
  3. model.add(layers.Dense(32,input_shape = (None,16),activation = tf.nn.relu))
  4. model.add(layers.Dense(10))
  5. # 显式添加layers.Activation激活层
  6. model.add(layers.Activation(tf.nn.softmax))
  7. model.summary()

2.4模型层(tf.keras.layers)

模型层可以使用内置的,也可以自定义。以下为内置的模型层。

2.4.1神经网络基础层

Dense、Input、Flatten、Activation、Dropout、BatchNormalization、Reshape形状重塑层。
DenseFeature:特征列接入层,用于接收一个特征列列表并产生一个密集连接层。
SpatialDropout2D:空间随机置零层。训练期间以一定几率将整个特征图置0,一种正则化手段,有利于避免特征图之间过高的相关性。
Concatenate拼接层、Add加法层、Substract减法层、Maximum取最大值层、Minimum取最小值层。

2.4.2卷积网络相关层

  • Conv1D:一维卷积,常用于文本。参数个数 = 输入通道数×卷积核尺寸(如3)×卷积核个数。
  • Conv2D:二维卷积,常用于图像。参数个数 = 输入通道数×卷积核尺寸(如3x3)×卷积核个数。
  • Conv3D:三维卷积,常用于视频。参数个数 = 输入通道数×卷积核尺寸(如3x3x3)×卷积核个数。
  • SeparableConv2D:二维深度可分离卷积层。不同于普通卷积同时对区域和通道操作,深度可分离卷积先操作区域,再操作通道。即先对每个通道做独立卷积即先操作区域,再用1乘1卷积跨通道组合即再操作通道。参数个数 = 输入通道数×卷积核尺寸 + 输入通道数×1×1×输出通道数。深度可分离卷积的参数数量一般远小于普通卷积,效果一般也更好。
  • DepthwiseConv2D:二维深度卷积层。仅有SeparableConv2D前半部分操作,即只操作区域,不操作通道,一般输出通道数和输入通道数相同,但也可以通过设置depth_multiplier让输出通道为输入通道的若干倍数。输出通道数 = 输入通道数 × depth_multiplier。参数个数 = 输入通道数×卷积核尺寸× depth_multiplier。
  • Conv2DTranspose:二维卷积转置层,俗称反卷积层。并非卷积的逆操作,但在卷积核相同的情况下,当其输入尺寸是卷积操作输出尺寸的情况下,卷积转置的输出尺寸恰好是卷积操作的输入尺寸。
  • LocallyConnected2D: 二维局部连接层。类似Conv2D,唯一的差别是没有空间上的权值共享,所以其参数个数远高于二维卷积。
  • MaxPooling2D: 二维最大池化层。也称作下采样层。池化层无参数,主要作用是降维。
  • AveragePooling2D: 二维平均池化层。
  • GlobalMaxPool2D: 全局最大池化层。每个通道仅保留一个值。一般从卷积层过渡到全连接层时使用,是Flatten的替代方案。
  • GlobalAvgPool2D: 全局平均池化层。每个通道仅保留一个值。

    2.4.3循环网络相关层

  • Embedding:嵌入层。一种比Onehot更加有效的对离散特征进行编码的方法。一般用于将输入中的单词映射为稠密向量。嵌入层的参数需要学习。

  • LSTM:长短记忆循环网络层。最普遍使用的循环网络层。具有携带轨道,遗忘门,更新门,输出门。可以较为有效地缓解梯度消失问题,从而能够适用长期依赖问题。设置return_sequences = True时可以返回各个中间步骤输出,否则只返回最终输出。
  • GRU:门控循环网络层。LSTM的低配版,不具有携带轨道,参数数量少于LSTM,训练速度更快。
  • SimpleRNN:简单循环网络层。容易存在梯度消失,不能够适用长期依赖问题。一般较少使用。
  • ConvLSTM2D:卷积长短记忆循环网络层。结构上类似LSTM,但对输入的转换操作和对状态的转换操作都是卷积运算。
  • Bidirectional:双向循环网络包装器。可以将LSTM,GRU等层包装成双向循环网络。从而增强特征提取能力。
  • RNN:RNN基本层。接受一个循环网络单元或一个循环单元列表,通过调用tf.keras.backend.rnn函数在序列上进行迭代从而转换成循环网络层。
  • LSTMCell:LSTM单元。和LSTM在整个序列上迭代相比,它仅在序列上迭代一步。可以简单理解LSTM即RNN基本层包裹LSTMCell。
  • GRUCell:GRU单元。和GRU在整个序列上迭代相比,它仅在序列上迭代一步。
  • SimpleRNNCell:SimpleRNN单元。和SimpleRNN在整个序列上迭代相比,它仅在序列上迭代一步。
  • AbstractRNNCell:抽象RNN单元。通过对它的子类化用户可以自定义RNN单元,再通过RNN基本层的包裹实现用户自定义循环网络层。
  • Attention:Dot-product类型注意力机制层。可以用于构建注意力模型。
  • AdditiveAttention:Additive类型注意力机制层。可以用于构建注意力模型。
  • TimeDistributed:时间分布包装器。包装后可以将Dense、Conv2D等作用到每一个时间片段上。

    2.5损失函数(tf.keras.losses)

    一般来说,监督学习的目标函数由损失函数和正则化项组成。(Objective = Loss + Regularization)
    正则化:可以使用L1正则或L2正则(regularizer),也可以直接约束参数的取值范围(constraint)。
    损失函数:可以使用内置损失函数,也可以自定义损失函数。

    2.5.1常用的一些内置损失函数

  • mean_squared_error(平方差误差损失,用于回归,简写为 mse, 类实现形式为 MeanSquaredError 和 MSE)

  • mean_absolute_error (绝对值误差损失,用于回归,简写为 mae, 类实现形式为 MeanAbsoluteError 和 MAE)
  • mean_absolute_percentage_error (平均百分比误差损失,用于回归,简写为 mape, 类实现形式为 MeanAbsolutePercentageError 和 MAPE)
  • Huber(Huber损失,只有类实现形式,用于回归,介于mse和mae之间,对异常值比较鲁棒,相对mse有一定的优势)
  • binary_crossentropy(二元交叉熵,用于二分类,类实现形式为 BinaryCrossentropy)
  • categorical_crossentropy(类别交叉熵,用于多分类,要求label为onehot编码,类实现形式为 CategoricalCrossentropy)
  • sparse_categorical_crossentropy(稀疏类别交叉熵,用于多分类,要求label为序号编码形式,类实现形式为 SparseCategoricalCrossentropy)
  • hinge(合页损失函数,用于二分类,最著名的应用是作为支持向量机SVM的损失函数,类实现形式为 Hinge)
  • kld(相对熵损失,也叫KL散度,常用于最大期望算法EM的损失函数,两个概率分布差异的一种信息度量。类实现形式为 KLDivergence 或 KLD)
  • cosine_similarity(余弦相似度,可用于多分类,类实现形式为 CosineSimilarity)

    2.6优化器(tf.keras.optimizers)

    深度学习优化算法大概经历了 SGD -> SGDM -> NAG ->Adagrad -> Adadelta(RMSprop) -> Adam -> Nadam 这样的发展历程。
    优化器主要使用apply_gradients方法传入变量和对应梯度从而来对给定变量进行迭代,或者直接使用minimize方法对目标函数进行迭代优化。

  • SGD, 默认参数为纯SGD, 设置momentum参数不为0实际上变成SGDM, 考虑了一阶动量, 设置 nesterov为True后变成NAG,即 Nesterov Acceleration Gradient,在计算梯度时计算的是向前走一步所在位置的梯度。

  • Adagrad, 考虑了二阶动量,对于不同的参数有不同的学习率,即自适应学习率。缺点是学习率单调下降,可能后期学习速率过慢乃至提前停止学习。
  • RMSprop, 考虑了二阶动量,对于不同的参数有不同的学习率,即自适应学习率,对Adagrad进行了优化,通过指数平滑只考虑一定窗口内的二阶动量。
  • Adadelta, 考虑了二阶动量,与RMSprop类似,但是更加复杂一些,自适应性更强。
  • Adam, 同时考虑了一阶动量和二阶动量,可以看成RMSprop上进一步考虑了Momentum。
  • Nadam, 在Adam基础上进一步考虑了 Nesterov Acceleration。

    2.7评估函数(tf.keras.metrics)

    评估指标可以使用内置的,也可以自定义评估指标。

    2.7.1内置的评估指标

  • MeanSquaredError(平方差误差,用于回归,可以简写为MSE,函数形式为mse)

  • MeanAbsoluteError (绝对值误差,用于回归,可以简写为MAE,函数形式为mae)
  • MeanAbsolutePercentageError (平均百分比误差,用于回归,可以简写为MAPE,函数形式为mape)
  • RootMeanSquaredError (均方根误差,用于回归)
  • Accuracy (准确率,用于分类,Accuracy=(TP+TN)/(TP+TN+FP+FN),要求y_true和y_pred都为类别序号编码)
  • Precision (精确率,用于二分类,Precision = TP/(TP+FP))
  • Recall (召回率,用于二分类,Recall = TP/(TP+FN))
  • TruePositives (真正例,用于二分类)
  • TrueNegatives (真负例,用于二分类)
  • FalsePositives (假正例,用于二分类)
  • FalseNegatives (假负例,用于二分类)
  • AUC(ROC曲线(TPR vs FPR)下的面积,用于二分类,直观解释为随机抽取一个正样本和一个负样本,正样本的预测值大于负样本的概率)
  • CategoricalAccuracy(分类准确率,与Accuracy含义相同,要求y_true(label)为onehot编码形式)
  • SparseCategoricalAccuracy (稀疏分类准确率,与Accuracy含义相同,要求y_true(label)为序号编码形式)
  • MeanIoU (Intersection-Over-Union,常用于图像分割)
  • TopKCategoricalAccuracy (多分类TopK准确率,要求y_true(label)为onehot编码形式)
  • SparseTopKCategoricalAccuracy (稀疏多分类TopK准确率,要求y_true(label)为序号编码形式)
  • Mean (平均值)
  • Sum (求和)

    2.8回调函数(tf.keras.callbacks)

    tf.keras的回调函数实际上是一个类,一般是在model.fit时作为参数指定,用于控制在训练过程开始或者在训练过程结束,在每个epoch训练开始或者训练结束,在每个batch训练开始或者训练结束时执行一些操作,例如收集一些日志信息,改变学习率等超参数,提前终止训练过程等等。

    2.8.1内置回调函数

  • BaseLogger: 收集每个epoch上metrics在各个batch上的平均值,对stateful_metrics参数中的带中间状态的指标直接拿最终值无需对各个batch平均,指标均值结果将添加到logs变量中。该回调函数被所有模型默认添加,且是第一个被添加的。

  • History: 将BaseLogger计算的各个epoch的metrics结果记录到history这个dict变量中,并作为model.fit的返回值。该回调函数被所有模型默认添加,在BaseLogger之后被添加。
  • EarlyStopping: 当被监控指标在设定的若干个epoch后没有提升,则提前终止训练。
  • TensorBoard: 为Tensorboard可视化保存日志信息。支持评估指标,计算图,模型参数等的可视化。
  • ModelCheckpoint: 在每个epoch后保存模型。
  • ReduceLROnPlateau:如果监控指标在设定的若干个epoch后没有提升,则以一定的因子减少学习率。
  • TerminateOnNaN:如果遇到loss为NaN,提前终止训练。
  • LearningRateScheduler:学习率控制器。给定学习率lr和epoch的函数关系,根据该函数关系在每个epoch前调整学习率。
  • CSVLogger:将每个epoch后的logs结果记录到CSV文件中。
  • ProgbarLogger:将每个epoch后的logs结果打印到标准输出流中。

    3.高阶API

    TensorFlow的高阶API主要为tf.keras.models提供的模型的类接口。

    3.1模型的构建

    使用Keras接口有以下3种方式构建模型:

  • 使用Sequential按层顺序构建模型。

  • 使用函数式API构建任意结构模型。

如果模型有多输入或者多输出,或者模型需要共享权重,或者模型具有残差连接等非顺序结构,推荐使用函数式API进行创建。

  • 继承Model基类构建自定义模型。

如果无特定必要,尽可能避免使用Model子类化的方式构建模型,这种方式提供了极大的灵活性,但也有更大的概率出错。

3.2模型的训练

模型的训练方法有:

  • 内置fit方法。

内置fit方法支持对numpy array, tf.data.Dataset以及 Python generator数据进行训练。
并且可以通过设置回调函数实现对训练过程的复杂控制逻辑。

  • 内置train_on_batch方法。

train_on_batch方法相比较fit方法更加灵活,可以不通过回调函数而直接在批次层次上更加精细地控制训练的过程。

  • 自定义训练循环。

自定义训练循环无需编译模型,使用自动微分机制(tf.GradientTape())直接利用优化器根据损失函数反向传播迭代参数,拥有最高的灵活性。

  • 单GPU训练模型。

当存在可用的GPU时,如果不特意指定device,tensorflow会自动优先选择使用GPU来创建张量和执行张量计算。
tensorflow默认获取全部GPU的全部内存资源权限,但实际上只使用一个GPU的部分资源,代码可以控制每个任务使用的GPU编号和显存大小。

  1. gpus = tf.config.list_physical_devices("GPU")
  2. if gpus:
  3. gpu0 = gpus[0] #如果有多个GPU,仅使用第0个GPU
  4. tf.config.experimental.set_memory_growth(gpu0, True) #设置GPU显存用量按需使用
  5. # 或者也可以设置GPU显存为固定使用量(例如:4G)
  6. #tf.config.experimental.set_virtual_device_configuration(gpu0,
  7. # [tf.config.experimental.VirtualDeviceConfiguration(memory_limit=4096)])
  8. tf.config.set_visible_devices([gpu0],"GPU")
  • 多GPU训练模型。

训练过程的耗时主要来自于两个部分,一部分来自数据准备,另一部分来自参数迭代。当参数迭代过程成为训练时间的主要瓶颈时,我们通常的方法是应用GPU或者Google的TPU来进行加速。
如果使用多GPU训练模型,推荐使用内置fit方法,较为方便。

  1. #此处在colab上使用1个GPU模拟出两个逻辑GPU进行多GPU训练
  2. gpus = tf.config.experimental.list_physical_devices('GPU')
  3. if gpus:
  4. # 设置两个逻辑GPU模拟多GPU训练
  5. try:
  6. tf.config.experimental.set_virtual_device_configuration(gpus[0],
  7. [tf.config.experimental.VirtualDeviceConfiguration(memory_limit=1024),
  8. tf.config.experimental.VirtualDeviceConfiguration(memory_limit=1024)])
  9. logical_gpus = tf.config.experimental.list_logical_devices('GPU')
  10. print(len(gpus), "Physical GPU,", len(logical_gpus), "Logical GPUs")
  11. except RuntimeError as e:
  12. print(e)
  13. #增加以下两行代码
  14. strategy = tf.distribute.MirroredStrategy()
  15. with strategy.scope():
  16. model = create_model()
  17. model.summary()
  18. model = compile_model(model)
  • TPU训练模型。
    1. #增加以下6行代码
    2. import os
    3. resolver = tf.distribute.cluster_resolver.TPUClusterResolver(tpu='grpc://' + os.environ['COLAB_TPU_ADDR'])
    4. tf.config.experimental_connect_to_cluster(resolver)
    5. tf.tpu.experimental.initialize_tpu_system(resolver)
    6. strategy = tf.distribute.experimental.TPUStrategy(resolver)
    7. with strategy.scope():
    8. model = create_model()
    9. model.summary()
    10. model = compile_model(model)

    3.3模型的部署

    TensorFlow训练好的模型以tensorflow原生方式保存成protobuf文件后可以用许多方式部署运行。
    例如:
    通过 tensorflow-js 可以用javascrip脚本加载模型并在浏览器中运行模型。
    通过 tensorflow-lite 可以在移动和嵌入式设备上加载并运行TensorFlow模型。
    通过 tensorflow-serving 可以加载模型后提供网络接口API服务,通过任意编程语言发送网络请求都可以获取模型预测结果。
    通过 tensorFlow for Java接口,可以在Java或者spark(scala)中调用tensorflow模型进行预测。

    3.3.1tensorflow-serving部署模型

    使用 tensorflow serving 部署模型要完成以下步骤。
    (1) 准备protobuf模型文件。
    训练模型并保存为protobuf文件。
    1. ## 将模型保存成pb格式文件
    2. export_path = "./data/linear_model/"
    3. version = "1" #后续可以通过版本号进行模型版本迭代与管理
    4. linear.save(export_path+version, save_format="tf")
    (2) 安装tensorflow serving。
    安装 tensorflow serving 有2种主要方法:通过Docker镜像安装,通过apt安装。
    (3) 启动tensorflow serving 服务。
    1. !docker run -t --rm -p 8501:8501 \
    2. -v "/Users/.../data/linear_model/" \
    3. -e MODEL_NAME=linear_model \
    4. tensorflow/serving & >server.log 2>&1
    (4) 向API服务发送请求,获取预测结果。
    可以使用任何编程语言的http功能发送请求,例如:linux的 curl 命令发送请求,以及Python的requests库发送请求。

    3.3.2使用spark(scala)调用tensorflow模型

    如果使用pyspark的话会比较简单,只需要在每个excutor上用Python加载模型分别预测就可以了。
    但工程上为了性能考虑,通常使用的是scala版本的spark。
    利用spark的分布式计算能力,从而可以让训练好的tensorflow模型在成百上千的机器上分布式并行执行模型推断。
    在spark(scala)中调用tensorflow模型进行预测需要完成以下几个步骤。
    (1)准备protobuf模型文件
    (2)创建spark(scala)项目,在项目中添加java版本的tensorflow对应的jar包依赖
    1. <!-- https://mvnrepository.com/artifact/org.tensorflow/tensorflow -->
    2. <dependency>
    3. <groupId>org.tensorflow</groupId>
    4. <artifactId>tensorflow</artifactId>
    5. <version>1.15.0</version>
    6. </dependency>
    (3)在spark(scala)项目中driver端加载tensorflow模型调试成功
    (4)在spark(scala)项目中通过RDD在excutor上加载tensorflow模型调试成功
    (5) 在spark(scala)项目中通过DataFrame在excutor上加载tensorflow模型调试成功

来源: 1.《30天吃掉TensorFlow》https://lyhue1991.github.io/eat_tensorflow2_in_30_days