29、使用 TensorFlow 构建航班准点预测模型

使用 TensorFlow 构建航班准点预测模型

在机器学习领域,使用 TensorFlow 构建模型是一项常见且重要的任务。本文将详细介绍如何使用 TensorFlow 构建一个航班准点预测的线性分类器模型,包括模型的搭建、训练、评估以及分布式训练等内容。

安装 TensorFlow

首先,你可以使用以下命令安装 TensorFlow:

pip install tensorflow
读取数据集并计算平均标签

我们可以通过命令行传入训练数据,调用 read_dataset() 方法读取数据集,然后使用 reduce_mean() 计算所有标签的平均值,示例代码如下:

# 解析命令行参数
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('--traindata', required=True)
args = parser.parse_args()
arguments = args.__dict__
traindata = arguments.pop('traindata')

# 读取数据集
feats, label = model.read_dataset(traindata)

# 计算平均标签
import tensorflow as tf
avg = tf.reduce_mean(label)
print(avg)

运行上述代码可以使用以下命令:

python task.py --traindata ~/data/flights/small.csv

如果遇到缺少 Python 包的错误,可以使用 pip 进行安装。若在 Cloud Datalab 中运行,它已经预装了 TensorFlow。不过,运行上述代码打印出的结果并非航班准点的总体概率,而是一个 Tensor 对象:

Tensor("Mean:0", shape=(), dtype=float32)

这是因为 TensorFlow 的工作方式,我们目前只是创建了计算图,还未实际执行该图。为了避免编写底层代码,我们将使用 TensorFlow 的 Experiment 类来控制模型的运行。

设置实验

Experiment 类可用于控制机器学习模型的训练和评估。创建 Experiment 实例时,需要提供一个估计器(如 LinearClassifier DNNClassifier 或自定义估计器),并指定模型所需的特征列。同时,要注册一个回调函数来读取训练数据, Experiment 会在 TensorFlow 会话中调用该函数。具体来说, Experiment 会以分布式方式调用模型的优化器,在每次读取一批训练示例时调整模型的权重。

构建 Experiment 所需的组件如下:
1. 机器学习模型 :包括特征列。
2. 训练输入函数 :用于读取训练数据,需返回一批特征及其对应的标签。
3. 评估输入函数 :类似于训练输入函数,但用于读取测试数据。
4. 导出策略和服务输入函数 :导出策略指定模型的保存时间,通常基于评估数据集保存最佳模型;服务输入函数用于在预测时读取输入。

线性分类器

在之前的工作中,我们基于三个连续变量(出发延迟、滑行时间和距离)构建了逻辑回归模型。当尝试添加出发机场这一分类变量时,由于需要进行独热编码,导致模型复杂度大幅增加,Spark ML 模型崩溃。

在这里,我们使用 TensorFlow 构建逻辑回归模型,并使用所有可用的列。逻辑回归本质上是一个带有 S 形输出节点的线性模型,在 TensorFlow 中由 LinearClassifier 类实现。

以下是定义特征列的代码:

import tensorflow.contrib.layers as tflayers

def get_features():
    real = {
        colname: tflayers.real_valued_column(colname)
        for colname in ('dep_delay,taxiout,distance,avg_dep_delay,avg_arr_delay' +
                        ',dep_lat,dep_lon,arr_lat,arr_lon').split(',')
    }
    sparse = {
        'carrier': tflayers.sparse_column_with_keys('carrier',
                                                    keys='AS,VX,F9,UA,US,WN,HA,EV,MQ,DL,OO,B6,NK,AA'.split(',')),
        'origin': tflayers.sparse_column_with_hash_bucket('origin',
                                                          hash_bucket_size=1000),  # FIXME
        'dest': tflayers.sparse_column_with_hash_bucket('dest',
                                                        hash_bucket_size=1000)  # FIXME
    }
    return real, sparse

对于连续特征,使用 RealValuedColumn 表示;对于离散特征(需要独热编码),使用 SparseColumn 表示。例如,航空公司代码使用 sparse_column_with_keys 指定特定的键,而出发机场和目的地机场代码使用 sparse_column_with_hash_bucket 进行哈希分桶处理。

创建线性模型的代码如下:

import tensorflow.contrib.learn as tflearn

def linear_model(output_dir):
    real, sparse = get_features()
    all = {}
    all.update(real)
    all.update(sparse)
    return tflearn.LinearClassifier(model_dir=output_dir,
                                    feature_columns=all.values())
训练和评估输入函数

训练和评估输入函数是提供给 Experiment 的回调函数,其签名必须与 Experiment 的调用方式匹配。训练输入函数的签名如下:

def input_fn():
    ...
    return features, labels

由于 read_dataset() 函数需要调用者提供训练文件名和其他可选参数,我们对其进行重构,使其返回一个具有所需签名的回调函数:

import tensorflow as tf

def read_dataset(filename, mode=tf.contrib.learn.ModeKeys.EVAL, batch_size=512,
                 num_training_epochs=10):
    # 实际传递给 TensorFlow 的输入函数
    def _input_fn():
        # 现有代码放在这里
        ...
        return features, labels
    return _input_fn

这样,我们可以将 read_dataset() 的结果传递给 Experiment 类。训练和评估输入函数可以通过以下方式获取:

train_input_fn = read_dataset(traindata,
                              mode=tf.contrib.learn.ModeKeys.TRAIN)
eval_input_fn = read_dataset(evaldata)

其中, mode 参数用于确保训练数据集被读取 num_training_epochs 次,而测试数据集仅读取一次。

服务输入函数

在预测时,输入值将通过 REST 调用传入,应用程序会以 JSON 字符串的形式提供所有输入参数。我们需要创建适当类型的占位符来接收这些参数:

import tensorflow as tf

def serving_input_fn():
    real, sparse = get_features()

    feature_placeholders = {
        key: tf.placeholder(tf.float32, [None])
        for key in real.keys()
    }
    feature_placeholders.update({
        key: tf.placeholder(tf.string, [None])
        for key in sparse.keys()
    })
    features = {
        # tf.expand_dims 会在张量形状中插入一个维度 1
        # 这将使输入张量成为一个大小为 1 的批次
        key: tf.expand_dims(tensor, -1)
        for key, tensor in feature_placeholders.items()
    }
    return tflearn.utils.input_fn_utils.InputFnOps(
        features,
        None,
        feature_placeholders)
创建实验

Experiment 在一个函数中创建,并将该函数作为回调提供给 learn_runner 主循环:

import tensorflow.contrib.learn as tflearn
from tensorflow.contrib.learn.python.learn.utils import saved_model_export_utils

def make_experiment_fn(traindata, evaldata, **args):
    def _experiment_fn(output_dir):
        return tflearn.Experiment(
            linear_model(output_dir),
            train_input_fn=read_dataset(traindata,
                                        mode=tf.contrib.learn.ModeKeys.TRAIN),
            eval_input_fn=read_dataset(evaldata),
            export_strategies=[saved_model_export_utils.make_export_strategy(
                serving_input_fn,
                default_output_alternative_key=None,
                exports_to_keep=1
            )],
            **args
        )
    return _experiment_fn

task.py 中,我们调用 learn_runner 主循环:

import tensorflow as tf
import tensorflow.contrib.learn as tflearn

# 设置日志级别
tf.logging.set_verbosity(tf.logging.INFO)

# 运行实验
learn_runner.run(model.make_experiment_fn(**arguments), output_dir)

至此, model.py task.py 文件完成。

进行训练运行

我们可以在包含 task.py 的目录中运行训练:

python task.py \
       --traindata ~/data/flights/train.csv \
       --output_dir ./trained_model \
       --evaldata ~data/flights/test.csv

若要作为 Python 模块运行,需要将模块路径添加到 Python 搜索路径,然后使用 python -m 调用:

export PYTHONPATH=${PYTHONPATH}:${PWD}/flights
python -m trainer.task \
   --output_dir=./trained_model \
  --traindata $DATA_DIR/train* --evaldata $DATA_DIR/test*

TensorFlow 在训练数据上训练模型,并在评估数据上评估训练好的模型。经过 200 步训练(每步包含一批输入)后,得到的评估指标如下:
| 指标 | 值 |
| — | — |
| accuracy | 0.922623 |
| accuracy/baseline_label_mean | 0.809057 |
| accuracy/threshold_0.500000_mean | 0.922623 |
| auc | 0.97447 |
| global_step | 200 |
| labels/actual_label_mean | 0.809057 |
| labels/prediction_mean | 0.744471 |
| loss | 0.312157 |
| precision/positive_threshold_0.500000_mean | 0.989173 |
| recall/positive_threshold_0.500000_mean | 0.91437 |

global_step 表示程序看到的批次数量。如果从检查点恢复训练, global_step 会从检查点继续递增。

我们关注的是概率阈值为 0.7 时的评估指标,因为当航班准点概率低于 70% 时,我们希望取消会议。目前没有方便的方法指定评估阈值,可通过以下方式修改阈值:

def linear_model(output_dir):
    real, sparse = get_features()
    all = {}
    all.update(real)
    all.update(sparse)
    estimator = tflearn.LinearClassifier(
        model_dir=output_dir, feature_columns=all.values())
    estimator.params["head"]._thresholds = [0.7]
    return estimator

修改阈值后,得到的评估指标如下:
| 指标 | 值 |
| — | — |
| accuracy | 0.922623 |
| accuracy/baseline_label_mean | 0.809057 |
| accuracy/threshold_0.700000_mean | 0.911527 |
| auc | 0.97447 |
| global_step | 200 |
| labels/actual_label_mean | 0.809057 |
| labels/prediction_mean | 0.744471 |
| loss | 0.312157 |
| precision/positive_threshold_0.700000_mean | 0.991276 |
| recall/positive_threshold_0.700000_mean | 0.898554 |

为了进行比较,我们还计算了均方根误差(RMSE)。在 Experiment 定义中添加评估指标:

import tensorflow.contrib.learn as tflearn
import tensorflow.contrib.metrics as tfmetrics

eval_metrics = {
    'rmse': tflearn.MetricSpec(metric_fn=my_rmse,
                               prediction_key='probabilities')
}

def my_rmse(predictions, labels, **args):
    prob_ontime = predictions[:, 1]
    return tfmetrics.streaming_root_mean_squared_error(prob_ontime,
                                                       labels, **args)

重新运行时,得到的评估结果包含 RMSE:
| 指标 | 值 |
| — | — |
| accuracy | 0.949015 |
| accuracy/baseline_label_mean | 0.809057 |
| accuracy/threshold_0.700000_mean | 0.944817 |
| auc | 0.973428 |
| global_step | 100 |
| labels/actual_label_mean | 0.809057 |
| labels/prediction_mean | 0.78278 |
| loss | 0.338125 |
| precision/positive_threshold_0.700000_mean | 0.985701 |
| recall/positive_threshold_0.700000_mean | 0.945508 |
| rmse | 0.208851 |

不过,这些结果仅基于 10000 个示例,我们需要在更大的数据集上进行训练和评估才能得出结论。

云分布式训练

在拥有使用 Experiment 类的 Python 模块后,进行完整数据集的分布式训练变得容易。我们可以使用 gcloud 命令将训练作业提交到 Cloud ML Engine,并指定 Google Cloud Storage 位置作为输入和输出:

#!/bin/bash
BUCKET=cloud-training-demos-ml
REGION=us-central1
OUTPUT_DIR=gs://${BUCKET}/flights/chapter9/output
DATA_DIR=gs://${BUCKET}/flights/chapter8/output
JOBNAME=flights_$(date -u +%y%m%d_%H%M%S)

gcloud ml-engine jobs submit training $JOBNAME \
  --region=$REGION \
  --module-name=trainer.task \
  --package-path=$(pwd)/flights/trainer \
  --job-dir=$OUTPUT_DIR \
  --staging-bucket=gs://$BUCKET \
  --scale-tier=STANDARD_1 \
  -- \
   --output_dir=$OUTPUT_DIR \
   --traindata $DATA_DIR/train* --evaldata $DATA_DIR/test*

参数大多显而易见,与本地执行时类似,只是 traindata evaldata 路径指向 Google Cloud Storage 桶。 scale-tier 表示作业所需的工作节点数量,目前 STANDARD_1 层级有 10 个工作节点。

我们可以通过修改处理文件的模式,在较小的数据集上进行实验:

PATTERN="Flights-00001*"
--traindata $DATA_DIR/train$PATTERN --evaldata $DATA_DIR/test$PATTERN

这样可以更快地进行实验。确定模型和特征后,再在整个数据集上运行最终模型。以下是实验结果的表格:
| 实验编号 | 模型 | 特征 | RMSE |
| — | — | — | — |
| 1 | 线性 | 所有输入原样使用 | 0.196 |
| 2 | 线性 | 与之前相同的三个输入变量 | 0.210 |
| 3 | 线性 | 添加第 8 章计算的两个时间聚合特征 | 0.204 |

从实验结果可以看出,额外的输入(时间平均值和额外的分类特征)对模型性能有很大帮助。同时,更多的数据对模型性能的提升作用大于新特征。虽然线性模型已经取得了一定的效果,但考虑到有大量的输入特征和大规模数据集,更复杂的模型可能会更好地利用这些特征,进一步提升模型性能。

综上所述,使用 TensorFlow 构建机器学习模型时,我们可以利用 Experiment 类方便地进行模型的训练、评估和分布式训练。通过不断实验和调整特征,能够逐步优化模型性能。在实际应用中,更多的数据往往比复杂的模型更能提升效果,但也可以尝试更复杂的模型来进一步挖掘数据的潜力。

使用 TensorFlow 构建航班准点预测模型

实验结果分析

从之前的实验结果表格可以清晰看出不同特征组合对模型性能的影响。为了更直观地展示各次实验的性能对比,我们绘制了如下的 mermaid 柱状图:

graph LR
    classDef bar fill:#F5EBFF,stroke:#BE8FED,stroke-width:2px;
    A(实验 1 RMSE: 0.196):::bar
    B(实验 2 RMSE: 0.210):::bar
    C(实验 3 RMSE: 0.204):::bar

通过这个柱状图,我们能更直观地比较各次实验的 RMSE 值。可以看到,实验 1 由于使用了所有输入特征,RMSE 最低,模型性能相对最好;实验 2 仅使用三个输入变量,RMSE 最高,性能最差;实验 3 在实验 2 的基础上添加了时间聚合特征,RMSE 有所降低,性能得到了一定提升。这充分说明了额外的输入特征对模型性能的改善有积极作用。

不同特征对模型性能的影响

为了深入分析不同特征对模型性能的影响,我们可以进一步细化实验。例如,单独分析时间聚合特征和额外的分类特征分别对模型的贡献。我们可以进行以下实验:
1. 仅添加时间聚合特征 :在仅使用三个输入变量的基础上,只添加第 8 章计算的两个时间聚合特征,观察模型性能的变化。
2. 仅添加额外的分类特征 :在仅使用三个输入变量的基础上,只添加机场相关的分类特征,观察模型性能的变化。

通过这些实验,我们可以更精确地了解每个特征对模型的影响,从而有针对性地选择和优化特征。

复杂模型的潜力

虽然线性模型在本次实验中已经取得了不错的效果,但考虑到有大量的输入特征和大规模数据集,更复杂的模型可能会更好地利用这些特征,进一步提升模型性能。例如,我们可以尝试使用深度神经网络(DNN)模型。

以下是一个简单的 DNN 模型示例代码:

import tensorflow.contrib.learn as tflearn

def dnn_model(output_dir):
    real, sparse = get_features()
    all = {}
    all.update(real)
    all.update(sparse)
    return tflearn.DNNClassifier(
        model_dir=output_dir,
        feature_columns=all.values(),
        hidden_units=[100, 50],
        n_classes=2
    )

在这个代码中,我们定义了一个包含两个隐藏层(分别有 100 和 50 个神经元)的 DNN 分类器。我们可以将这个模型替换之前的线性模型,重新进行训练和评估,观察性能的变化。

模型优化策略

为了进一步优化模型性能,我们可以采用以下策略:
1. 特征工程 :除了使用现有的特征,还可以尝试创建新的特征,例如特征之间的交互项、多项式特征等。
2. 超参数调优 :使用网格搜索、随机搜索等方法,调整模型的超参数,如学习率、批次大小、隐藏层神经元数量等。
3. 正则化 :在模型中添加正则化项,如 L1 或 L2 正则化,防止模型过拟合。

总结

通过本次实验,我们深入了解了如何使用 TensorFlow 构建航班准点预测模型。从安装 TensorFlow 到读取数据集、构建模型、进行训练和评估,再到云分布式训练,我们完成了一个完整的机器学习流程。

在实验过程中,我们发现额外的输入特征对模型性能有显著的提升作用,更多的数据往往比复杂的模型更能提升效果。但同时,复杂模型在处理大规模数据集和大量输入特征时也具有很大的潜力。

在实际应用中,我们可以根据具体情况选择合适的模型和特征,通过不断实验和优化,逐步提升模型性能,为航班准点预测提供更准确的结果。

以下是本次实验的总结表格:
| 方面 | 内容 |
| — | — |
| 模型类型 | 线性模型、DNN 模型 |
| 特征使用 | 连续特征、离散特征、时间聚合特征、额外分类特征 |
| 优化策略 | 特征工程、超参数调优、正则化 |
| 实验结论 | 额外特征和数据对性能提升重要,复杂模型有潜力 |

希望这些内容能为你在机器学习领域的实践提供有价值的参考,让你在构建和优化模型的过程中更加得心应手。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值