从神经网络迁移

TensorFlow 决策森林 (TF-DF) 是 TensorFlow 中提供的一组决策森林 (DF) 算法。决策森林的工作方式不同于神经网络 (NN):DF 通常不使用反向传播或小批量进行训练。因此,TF-DF 管道与其他 TensorFlow 管道有一些区别。

本文档列出了这些区别,并提供了一个指南,用于将 TF 管道更新为使用 TF-DF

本文档假设您熟悉 初学者协作

数据集和特征

验证数据集

与标准神经网络训练范式不同,TF-DF 模型不需要验证数据集来监控过拟合或提前停止训练。如果您已经有了训练/验证/测试拆分,并且您出于上述原因之一使用验证,那么您可以安全地将 TF-DF 训练在训练+验证上(除非验证拆分也用于其他目的,例如超参数调整)。

- model.fit(train_ds, validation_data=val_ds)
+ model.fit(train_ds.concatenate(val_ds))

# Or just don't create a validation dataset

理由:TF-DF 框架由多种算法组成。其中一些算法不使用验证数据集(例如随机森林),而另一些算法则使用(例如梯度提升树)。使用验证数据集的算法可能会从不同类型和大小的验证数据集中受益。因此,如果需要验证数据集,它将从训练数据集中自动提取。

数据集 I/O

精确训练 1 个 epoch

# Number of epochs in Keras
- model.fit(train_ds, num_epochs=5)

# Number of epochs in the dataset
- train_ds = train_ds.repeat(5)
- model.fit(train_ds)
+ model.fit(train_ds)

理由:神经网络用户通常将模型训练 N 步(这可能涉及循环遍历数据集 > 1 次),因为 SGD 的性质。TF-DF 通过读取整个数据集,然后在最后运行训练来进行训练。需要 1 个 epoch 来读取完整数据集,任何额外的步骤都会导致不必要的数据 I/O,以及更慢的训练速度。

不要对数据集进行混洗

数据集不需要进行混洗(除非 input_fn 仅读取数据集的样本)。

- train_ds = train_ds.shuffle(5)
- model.fit(train_ds)
+ model.fit(train_ds)

理由:TF-DF 在将完整数据集读入内存后,会在内部对数据访问进行混洗。TF-DF 算法是确定性的(如果用户不更改随机种子)。启用混洗只会使算法变得非确定性。如果输入数据集是有序的,并且 input_fn 仅读取它的样本(样本应该是随机的),那么混洗是有意义的。但是,这将使训练过程变得非确定性。

不要调整批次大小

批次大小不会影响模型质量

- train_ds = train_ds.batch(hyper_parameter_batch_size())
- model.fit(train_ds)
# The batch size does not matter.
+ train_ds = train_ds.batch(64)
+ model.fit(train_ds)

理由:由于 TF-DF 始终在读取完整数据集后对其进行训练,因此模型质量不会因批次大小而异(与 SGD 等小批量训练算法不同,在 SGD 中,学习率等参数需要共同调整)。因此,它应该从超参数扫描中删除。批次大小只会影响数据集 I/O 的速度。

大型数据集

与神经网络不同,神经网络可以无限地循环遍历大型数据集的小批量,决策森林需要一个有限的数据集,该数据集在训练过程中适合内存。数据集的大小对性能和内存有影响。

增加数据集的大小会产生边际收益递减,并且 DF 算法可能比大型 NN 模型需要更少的示例来收敛。您可以尝试扩展数据量来查看计算权衡何时变得合理,而不是扩展训练步骤的数量(如 NN 中那样)。因此,最好先尝试对数据集的(小)子集进行训练。

另一种解决方案是使用分布式训练。如果有多台机器可用,分布式训练是增加数据集大小的好方法。虽然所有分布式算法都可用于分布计算,但并非所有算法都能分布 RAM 使用情况。有关更多详细信息,请查看 文档

要使用多少个示例

它应该适合模型正在训练的机器的内存:

  • 请注意,这与磁盘上的示例大小不同。

  • 一般来说,一个数值或分类值使用 4 字节的内存。因此,具有 100 个特征和 2500 万个示例的数据集将占用约 10GB (= 100 * 25 *10^6 * 4 字节) 的内存。

  • 分类集特征(例如标记化的文本)占用更多内存(每个标记 4 字节 + 每个特征 12 字节)。

考虑您的训练时间预算

  • 虽然通常比 NN 针对较小的数据集(例如 <100k 个示例)更快,但 DF 训练算法不会随着数据集大小线性扩展;在大多数情况下,它大约为 ~O(特征 x 示例数量 x log(示例数量))。

  • 训练时间取决于超参数。影响最大的参数是:(1)树的数量(num_trees),(2)样本采样率(GBT 的 subsample),以及(3)属性采样率(num_candidate_attributes_ratio)。

  • 分类集特征比其他特征更昂贵。成本由 categorical_set_split_greedy_sampling 参数控制。

  • 稀疏斜特征(默认情况下禁用)可以提供良好的结果,但计算成本很高。

数据扩展的经验法则

建议从一小部分数据(<10k 个样本)开始,这应该允许您在大多数情况下在几秒钟或几分钟内训练一个 TF-DF 模型。然后,您可以以固定速率(例如,每次增加 40%)增加数据,直到验证集性能不再提高或数据集不再适合内存。

特征归一化/预处理

不要使用特征列转换数据

TF-DF 模型不需要显式提供特征语义和转换。默认情况下,数据集中的所有特征(标签除外)都将被检测到并由模型使用。特征语义将自动检测,如果需要,可以手动覆盖。

# Estimator code
- feature_columns = [
-   tf.feature_column.numeric_column(feature_1),
-   tf.feature_column.categorical_column_with_vocabulary_list(feature_2, ['First', 'Second', 'Third'])
-   ]
- model = tf.estimator.LinearClassifier(feature_columns=feature_columnes)
# Use all the available features. Detect the type automatically.
+ model = tfdf.keras.GradientBoostedTreesModel()

您还可以指定输入特征的子集

+ features = [
+   tfdf.keras.FeatureUsage(name="feature_1"),
+   tfdf.keras.FeatureUsage(name="feature_2")
+   ]
+ model = tfdf.keras.GradientBoostedTreesModel(features=features, exclude_non_specified_features=True)

如有必要,您可以强制特征的语义。

+ forced_features = [
+   tfdf.keras.FeatureUsage(name="feature_1", semantic=tfdf.keras.FeatureSemantic.CATEGORICAL),
+   ]
+ model = tfdf.keras.GradientBoostedTreesModel(features=features)

理由:虽然某些模型(如神经网络)需要标准化的输入层(例如,不同特征类型到嵌入的映射),但 TF-DF 模型可以原生使用分类和数值特征,以及根据数据自动检测特征的语义类型。

不要预处理特征

决策树算法不会从用于神经网络的一些经典特征预处理中受益。下面,明确列出了一些更常见的特征处理策略,但安全的起点是删除所有旨在帮助神经网络训练的预处理。

不要归一化数值特征

- def zscore(value):
-   return (value-mean) / sd

- feature_columns = [tf.feature_column.numeric_column("feature_1",normalizer_fn=zscore)]

理由:决策森林算法原生支持非归一化的数值特征,因为分割算法不会对输入进行任何数值转换。某些类型的归一化(例如,z 分数归一化)不会帮助训练过程的数值稳定性,而某些类型(例如,异常值剪切)可能会损害最终模型的表达能力。

不要对分类特征进行编码(例如,哈希、独热或嵌入)

- integerized_column = tf.feature_column.categorical_column_with_hash_bucket("feature_1",hash_bucket_size=100)
- feature_columns = [tf.feature_column.indicator_column(integerized_column)]
- integerized_column = tf.feature_column.categorical_column_with_vocabulary_list('feature_1', ['bob', 'george', 'wanda'])
- feature_columns = [tf.feature_column.indicator_column(integerized_column)]

理由:TF-DF 原生支持分类特征,并将“转换”的词汇表项视为其内部词汇表中的另一个项(可以通过模型超参数配置)。某些转换(如哈希)可能会造成损失。嵌入不支持,除非它们是预训练的,因为决策森林模型不可微分(参见 中级 colab)。请注意,特定领域的词汇策略(例如,停用词去除、文本规范化)可能仍然有用。

如何处理文本特征

TF-DF 原生支持 分类集特征。因此,标记化 n 元组的词袋可以原生使用。

或者,文本也可以通过 预训练的嵌入 使用。

分类集在小型数据集上样本效率高,但在大型数据集上训练成本高。将分类集和预训练的嵌入相结合通常比单独使用任何一种方法都能获得更好的结果。

不要用魔法值替换缺失的特征

理由:TF-DF 原生支持缺失值。与神经网络不同,神经网络如果输入中存在 NaN,可能会将 NaN 传播到梯度,TF-DF 如果算法看到缺失值和哨兵值之间的差异,将进行最佳训练。

- feature_columns = [
- tf.feature_column.numeric_column("feature_1", default_value=0),
- tf.feature_column.numeric_column("feature_1_is_missing"),
- ]

处理图像和时间序列

在决策森林中没有用于使用图像或时间序列特征的标准算法,因此需要一些额外的工作才能使用它们。

理由:卷积、LSTM、注意力和其他序列处理算法是特定于神经网络的架构。

可以使用以下策略处理这些特征

  • 特征工程

    • 图像:在某些时候,使用随机森林处理图像很流行(例如,

      微软 Kinect,但如今,神经网络是最新技术。

    • 时间序列:[移动统计] 对于样本相对较少的时间序列数据(例如,医疗领域的生命体征)可以非常有效。

    • 嵌入模块:神经网络嵌入模块可以为决策森林算法提供丰富的特征。 中级 colab 展示了如何将 tf-hub 嵌入和 TF-DF 模型结合起来。

训练管道

不要使用硬件加速器,例如 GPU、TPU

TF-DF 训练(目前)不支持硬件加速器。所有训练和推理都在 CPU 上完成(有时使用 SIMD)。

请注意,TF-DF 在 CPU 上的推理(尤其是在使用 Yggdrasil C++ 库进行服务时)可以非常快(每个 CPU 内核每个样本亚微秒)。

不要使用检查点或训练中挂钩

TF-DF(目前)不支持模型检查点,这意味着期望模型在训练完成之前可用的挂钩在很大程度上不受支持。模型只有在训练了请求的树木数量(或提前停止)后才能使用。

依赖于训练步骤的 Keras 挂钩也不起作用 - 由于 TF-DF 训练的性质,模型在第一个纪元的末尾进行训练,并且在该纪元之后将保持不变。该步骤仅对应于数据集 I/O。

模型确定性

TF-DF 训练算法是确定性的,即在相同数据集上训练两次将得到完全相同的模型。这与使用 TensorFlow 训练的神经网络不同。为了保持这种确定性,用户应确保数据集读取也是确定性的。

训练配置

指定任务(例如,分类、排序)而不是损失(例如,二元交叉熵)

- model = tf_keras.Sequential()
- model.add(Dense(64, activation=relu))
- model.add(Dense(1)) # One output for binary classification

- model.compile(loss=tf_keras.losses.BinaryCrossentropy(from_logits=True),
-               optimizer='adam',
-               metrics=['accuracy'])
# The loss is automatically determined from the task.
+ model = tfdf.keras.GradientBoostedTreesModel(task=tf_keras.Task.CLASSIFICATION)

# Optional if you want to report the accuracy.
+ model.compile(metrics=['accuracy'])

理由:并非所有 TF-DF 学习算法都使用损失。对于那些使用损失的算法,损失将根据任务自动检测并在模型摘要中打印。您也可以使用损失超参数覆盖它。

超参数在语义上是稳定的

所有超参数都有默认值。这些值是合理的第一个尝试候选。默认超参数值保证永远不会改变。因此,默认情况下会禁用新的超参数或算法改进。

希望使用最新算法但不想自己优化超参数的用户可以使用 TF-DF 提供的“超参数模板”。新的超参数模板将在软件包更新时发布。

# Model with default hyper-parameters.
model = tfdf.keras.GradientBoostedTreesModel()

# List the hyper-parameters (with default value) and hyper-parameters templates of the GBT learning algorithm (in colab)
?tfdf.keras.GradientBoostedTreesModel

# Use a hyper-parameter template.
model = tfdf.keras.GradientBoostedTreesModel(hp_template="winner_1")

# Change one of the hyper-parameters.
model = tfdf.keras.GradientBoostedTreesModel(num_trees=500)

# List all the learning algorithms available
tfdf.keras.get_all_models()

模型调试

本节介绍了一些查看/调试/解释模型的方法。 初学者 colab 包含一个端到端的示例。

简单的模型摘要

# Text description of the model, training logs, feature importances, etc.
model.summary()

训练日志和 Tensorboard

# List of metrics
logs = model.make_inspector().training_logs()
print(logs)

或使用 TensorBoard

% load_ext
tensorboard
model.make_inspector().export_to_tensorboard("/tmp/tensorboard_logs")
% tensorboard - -logdir
"/tmp/tensorboard_logs"

特征重要性

model.make_inspector().variable_importances()

绘制树

tfdf.model_plotter.plot_model_in_colab(model, tree_idx=0)

访问树结构

tree = model.make_inspector().extract_tree(tree_idx=0)
print(tree)

(参见 高级 colab

不要使用 TensorFlow 分布式策略

TF-DF 尚未支持 TF 分布式策略。多工作器设置将被忽略,训练仅在管理器上进行。

- with tf.distribute.MirroredStrategy():
-    model = ...
+ model = ....

堆叠模型

TF-DF 模型不会反向传播梯度。因此,除非 NN 已经过训练,否则它们不能与 NN 模型组合。

从 tf.estimator.BoostedTrees {Classifier/Regressor/Estimator} 迁移

尽管听起来很相似,但 TF-DF 和 Estimator 增强树是不同的算法。TF-DF 实现了经典的 随机森林梯度增强机(使用树) 论文。tf.estimator.BoostedTreesEstimator 是一种近似的梯度增强树算法,具有 这篇论文 中描述的小批量训练过程。

一些超参数具有相似的语义(例如,num_trees),但它们具有不同的质量含义。如果您在 tf.estimator.BoostedTreesEstimator 上调整了超参数,则需要在 TF-DF 中重新调整超参数才能获得最佳结果。

对于 Yggdrasil 用户

Yggdrasil 决策森林 是 TF-DF 使用的核心训练和推理库。训练配置和模型是交叉兼容的(即,使用 TF-DF 训练的模型可以使用 Yggdrasil 推理)。

但是,一些 Yggdrasil 算法(尚未)在 TF-DF 中可用。

  • 具有分片采样的梯度增强树。