TensorFlow Decision Forests (TF-DF) 是 TensorFlow 中提供的一系列决策森林 (DF) 算法集合。决策森林的工作原理与神经网络 (NN) 不同:DF 通常不使用反向传播或小批量(mini-batch)方式进行训练。因此,TF-DF 流水线与其他 TensorFlow 流水线存在一些差异。
本文档列出了这些差异,并指导如何将 TF 流水线更新为使用 TF-DF。
本文档假设您熟悉入门 Colab。
数据集和特征
验证数据集
与标准的神经网络训练范式不同,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 以及更慢的训练速度。
不要打乱(Shuffle)数据集
不需要打乱数据集(除非 input_fn 只读取数据集的一个采样)。
- train_ds = train_ds.shuffle(5)
- model.fit(train_ds)
+ model.fit(train_ds)
原理:在将完整数据集读取到内存后,TF-DF 会在内部打乱对数据的访问。TF-DF 算法是确定性的(如果用户不更改随机种子)。启用打乱只会使算法变得不确定。如果输入数据集是有序的,并且 input_fn 只读取其中的一个采样(该采样应该是随机的),那么进行打乱才有意义。然而,这将使训练过程变得不确定。
不要调整批处理大小(Batch size)
批处理大小不会影响模型质量
- 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 等小批量训练算法不同,后者需要联合调整学习率等参数)。因此,应将其从超参数搜索中移除。批处理大小只会影响数据集 I/O 的速度。
大型数据集
与可以无限循环遍历大型数据集的小批量的神经网络不同,决策森林需要一个适合内存的有限数据集来进行训练过程。数据集的大小会对性能和内存产生影响。
增加数据集大小会带来边际收益递减,而且 DF 算法在收敛时所需的示例往往比大型 NN 模型少。与其缩放训练步骤的数量(如在 NN 中),您可以尝试缩放数据量,以查看计算权衡在何处有意义。因此,首先尝试在数据集的一个(小)子集上进行训练是一个好主意。
另一种解决方案是使用分布式训练。如果有多台机器可用,分布式训练是增加数据集大小的好方法。虽然所有分布式算法都可以用于分发计算,但并非所有算法都能分发内存使用量。有关更多详细信息,请查看文档。
要使用多少个示例
它应该适合模型训练所在机器的内存。:
请注意,这与磁盘上示例的大小不同。
经验法则是,一个数值或分类值使用 4 字节的内存。因此,一个包含 100 个特征和 2500 万个示例的数据集将占用约 10GB (= 100 * 25 * 10^6 * 4 字节) 的内存。
分类集特征(例如标记化的文本)占用更多内存(每个标记 4 字节 + 每个特征 12 字节)。
考虑您的训练时间预算
虽然对于较小的数据集(例如 <100k 个示例)通常比 NN 更快,但 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)]
原理:决策森林算法原生支持非归一化的数值特征,因为分裂算法不对输入进行任何数值转换。某些类型的归一化(例如 zscore 归一化)不会帮助训练过程的数值稳定性,而某些(例如异常值裁剪)可能会损害最终模型的表现力。
不要对分类特征进行编码(例如哈希、独热或嵌入)
- 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-gram 包可以被原生消费。
或者,文本也可以通过 预训练嵌入 消费。
分类集在小型数据集上采样效率高,但在大型数据集上训练成本高。结合分类集和预训练嵌入通常比单独使用其中任何一个能产生更好的结果。
不要用特殊值替换缺失的特征
原理: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、注意力机制和其他序列处理算法是神经网络特定的架构。
可以使用以下策略处理这些特征:
特征工程
训练流水线
不要使用硬件加速器,例如 GPU、TPU
TF-DF 训练(目前)不支持硬件加速器。所有训练和推理都在 CPU 上完成(有时使用 SIMD)。
请注意,CPU 上的 TF-DF 推理(特别是在使用 Yggdrasil C++ 库提供服务时)可以非常快(每个 CPU 核心每示例亚微秒级)。
不要使用检查点或训练中挂钩
TF-DF(目前)不支持模型检查点,这意味着期望模型在训练完成前可用的挂钩大多不受支持。模型只有在训练完所需的树数量(或提前停止)后才可用。
依赖训练步骤的 Keras 挂钩也将无法工作——由于 TF-DF 训练的性质,模型在第一个 epoch 结束时进行训练,之后模型将保持不变。该步骤仅对应于数据集 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 = ....
模型堆叠(Stacking)
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 Decision Forest 是 TF-DF 使用的核心训练和推理库。训练配置和模型是交叉兼容的(即使用 TF-DF 训练的模型可以与 Yggdrasil 推理一起使用)。
然而,一些 Yggdrasil 算法(尚)在 TF-DF 中不可用。
- 具有分片采样(sharded sampling)的梯度提升树。