28、航班数据处理与机器学习模型构建

航班数据处理与机器学习模型构建

1. 数据处理中的重复问题及解决

在数据处理过程中,使用滑动窗口计算平均到达延迟时出现了重复数据的问题。原本在 Cloud Storage 上的结果看似正确,但仔细检查后发现存在大量重复的航班信息。

1.1 重复问题的原因

这是由于使用了滑动窗口,每 5 分钟计算一次,一小时内会有多个重叠的窗口。每个航班会出现在多个重叠的窗格中,导致每次落入窗格时都会重复输出。例如,一个航班会在 12 个不同的窗格中出现(因为 60 分钟除以 5 分钟等于 12)。

1.2 解决方法

为了解决这个问题,插入了一个转换操作 InLatestSlice ,确保航班仅在窗口的最新切片中输出。以下是具体的代码实现:

.apply("InLatestSlice", ParDo.of(new DoFn<Flight, Flight>() {
    @ProcessElement
    public void processElement(ProcessContext c, 
    IntervalWindow window) throws Exception {
        Instant endOfWindow = window.maxTimestamp();
        Instant flightTimestamp = c.element().getEventTimestamp();
        long msecs = endOfWindow.getMillis() -
        flightTimestamp.getMillis();
        long THRESH = 5 * 60 * 1000; // 5 minutes
        if (msecs < THRESH) {
            c.output(c.element());
        }
    }
}))

通过这个转换,输出的数据变得正确且处理速度加快。整个管道使用 50 个工作节点处理完整数据集时,大约 20 分钟就能完成。

1.3 管道生成的文件

管道最终生成了三组文件:
- delays.csv :包含训练数据集上每个机场 - 小时组合的历史出发延迟信息。例如,在纽约肯尼迪机场下午 5:15 的平均出发延迟可以从 JFK:17 的条目中查找,为 37.47 分钟。
- train*.csv :作为机器学习的训练数据集,用于根据其他列(出发延迟、滑行时间、距离、平均出发和到达延迟等)预测第一列(航班是否准点)。
- eval*.csv :与训练文件格式相同,用于评估机器学习模型。

2. 机器学习模型面临的问题及解决方案

在之前构建机器学习模型时,遇到了一些问题。虽然解决了使用时间窗口聚合特征时的训练 - 服务偏差问题,但仍存在以下三个问题:
- 独热编码分类列导致数据集规模爆炸。
- 嵌入需要特殊的记录管理。
- 将模型投入生产需要机器学习库能够在训练集群之外的环境中移植。

2.1 解决方案 - TensorFlow

TensorFlow 是谷歌开发的开源机器学习库,能够满足这些需求。它具有以下优点:
- 强大到足以进行分布式训练,能够处理非常大的数据集。
- 灵活支持最新的机器学习研究,如宽深网络。
- 可移植,支持在自定义专用集成电路(ASIC)上进行大规模并行预测,也能在手持设备上进行预测。

2.2 迈向更复杂的模型

传统编程需要为计算机编写明确的规则来完成任务,而机器学习则是让计算机从大量数据中学习决策。例如,通过展示 5000 张好螺丝和 5000 张坏螺丝的图像,让计算机学习区分好坏螺丝。

在机器学习中,有多种模型可供选择,如随机森林、支持向量机和神经网络。对于大多数实际问题,寻找更多的训练数据或设计更好的输入特征往往比更换模型带来更大的收益。但对于具有高度密集和高度相关输入的特定问题,如音频和图像,深度神经网络表现出色。

对于航班延误问题,采用了“宽深”模型,包括一个用于稀疏输入特征的宽线性部分和一个用于连续输入特征的深度层部分。

2.3 训练模型的选择

使用 TensorFlow 训练模型,其核心代码用 C++ 编写,允许将计算部署到一个或多个 CPU 或 GPU 上。预测时,训练好的模型可以在 CPU、GPU、使用谷歌定制 ASIC 芯片(Tensor Processing Units 或 TPUs)的服务器甚至移动设备上运行。而且,无需使用 C++ 编程,通过构建数据流图并将数据流入图中即可进行操作。

2.4 神经网络的构建与调整

首先构建一个简单的神经网络,将逻辑回归表示为只有一个节点的神经网络,以便与之前的模型进行比较。之后,构建一个具有更多节点和层的神经网络,输出节点使用 sigmoid 函数将输出限制在 [0,1] 范围内,中间层使用修正线性单元(ReLU)作为激活函数。

使用 ReLU 激活函数有其优缺点。优点是训练速度快,能够得到稀疏模型;缺点是神经元的输出可能会达到非常大的正值。因此,在过去几年中,机器学习领域有一些关于如何初始化和训练 ReLU 网络的理论进展。

3. 数据读取到 TensorFlow

3.1 准备数据集

在开发代码时,为了方便在笔记本电脑上进行开发,创建了一个较小的数据集。具体操作步骤如下:

mkdir -p ~/data/flights
BUCKET=cloud-training-demos-ml
gsutil cp \  
  gs://${BUCKET}/flights/chapter8/output/trainFlights-00001*.csv \
  full.csv
head -10003 full.csv > ~/data/flights/train.csv
rm full.csv

选择 10003 这个数字是为了确保代码能正确处理不完整的批次。重复上述脚本,使用 testFlights-00001*.csv 创建 test.csv 文件。

3.2 创建目录结构

为了将 TensorFlow 程序提交到 Cloud ML Engine 运行,需要创建一个 Python 模块的目录结构:

flights
flights/trainer
flights/trainer/__init__.py

__init__.py 文件虽然为空,但对于 trainer 作为 Python 模块正常工作是必需的。

3.3 编写读取数据的代码

model.py 文件中编写读取数据的函数。首先导入 tensorflow 包,并定义 CSV 文件的列头和默认值:

import tensorflow as tf
CSV_COLUMNS  = \
('ontime,dep_delay,taxiout,distance,avg_dep_delay,avg_arr_delay' + \
 'carrier,dep_lat,dep_lon,arr_lat,arr_lon,origin,dest').split(',')
LABEL_COLUMN = 'ontime'
DEFAULTS     = [[0.0],[0.0],[0.0],[0.0],[0.0],[0.0],\
                ['na'],[0.0],[0.0],[0.0],[0.0],['na'],['na']]

然后编写 read_dataset 函数,该函数用于读取训练数据,每次返回 batch_size 个示例,并在 num_training_epochs 次内遍历整个训练集:

def read_dataset(filename, mode=tf.contrib.learn.ModeKeys.EVAL,
                 batch_size=512, num_training_epochs=10):
    num_epochs = num_training_epochs \
        if mode == tf.contrib.learn.ModeKeys.TRAIN else 1
    input_file_names = tf.train.match_filenames_once(filename)
    filename_queue = tf.train.string_input_producer(
        input_file_names, num_epochs=num_epochs, shuffle=True)
    reader = tf.TextLineReader()
    _, value = reader.read_up_to(filename_queue, num_records=batch_size)
    value_column = tf.expand_dims(value, -1)
    columns = tf.decode_csv(value_column, record_defaults=DEFAULTS)
    features = dict(zip(CSV_COLUMNS, columns))
    label = features.pop(LABEL_COLUMN)
    return features, label

3.4 数据读取方式的权衡

在这个示例中,使用 TensorFlow 的原生操作读取 CSV 文件。这是在人类可读性和高性能之间的权衡。最快的读取方式是将数据存储为 TFRecord 文件,但目前没有可视化或调试工具能读取该格式。最方便的方式是直接从 numpy 数组构建 tf.Constants ,但不适用于内存外的数据集。存储和读取 CSV 文件是一种折中的方式,既能使用可视化和调试工具,又能保证较快的读取速度。

3.5 调用读取数据的函数

使用 Python 的 argparse 库编写 main 函数,以便通过命令行参数传入输入文件的名称:

if __name__ == '__main__':
    parser = argparse.ArgumentParser()
    parser.add_argument(
        '--traindata',
        help='Training data file(s)'
    )

通过以上步骤,完成了从数据处理到机器学习模型构建和数据读取的一系列操作,为后续的模型训练和评估奠定了基础。

以下是一个简单的 mermaid 流程图,展示数据处理和模型构建的主要流程:

graph LR
    A[读取航班数据] --> B[过滤数据]
    B --> C[计算出发延迟]
    C --> D[计算到达延迟]
    D --> E[去除重复数据]
    E --> F[生成训练和评估数据集]
    F --> G[构建机器学习模型]
    G --> H[训练模型]
    H --> I[评估模型]

以下是一个表格,总结不同数据读取方式的优缺点:
| 读取方式 | 优点 | 缺点 |
| ---- | ---- | ---- |
| TFRecord 文件 | 读取速度快 | 无可视化和调试工具 |
| 从 numpy 数组构建 tf.Constants | 方便 | 不适用于内存外数据集 |
| CSV 文件 | 可使用可视化和调试工具,读取速度较快 | 性能不如 TFRecord 文件 |

3.6 完整代码示例与解析

下面是一个完整的示例代码,将上述的各个部分整合起来,展示如何使用 TensorFlow 读取数据并进行简单的训练:

import tensorflow as tf
import argparse

# 定义 CSV 列头和默认值
CSV_COLUMNS = \
    ('ontime,dep_delay,taxiout,distance,avg_dep_delay,avg_arr_delay' +
     'carrier,dep_lat,dep_lon,arr_lat,arr_lon,origin,dest').split(',')
LABEL_COLUMN = 'ontime'
DEFAULTS = [[0.0], [0.0], [0.0], [0.0], [0.0], [0.0],
            ['na'], [0.0], [0.0], [0.0], [0.0], ['na'], ['na']]


# 读取数据集的函数
def read_dataset(filename, mode=tf.contrib.learn.ModeKeys.EVAL,
                 batch_size=512, num_training_epochs=10):
    num_epochs = num_training_epochs \
        if mode == tf.contrib.learn.ModeKeys.TRAIN else 1
    input_file_names = tf.train.match_filenames_once(filename)
    filename_queue = tf.train.string_input_producer(
        input_file_names, num_epochs=num_epochs, shuffle=True)
    reader = tf.TextLineReader()
    _, value = reader.read_up_to(filename_queue, num_records=batch_size)
    value_column = tf.expand_dims(value, -1)
    columns = tf.decode_csv(value_column, record_defaults=DEFAULTS)
    features = dict(zip(CSV_COLUMNS, columns))
    label = features.pop(LABEL_COLUMN)
    return features, label


# 主函数
if __name__ == '__main__':
    parser = argparse.ArgumentParser()
    parser.add_argument(
        '--traindata',
        help='Training data file(s)'
    )
    args = parser.parse_args()

    # 读取训练数据
    features, label = read_dataset(args.traindata, mode=tf.contrib.learn.ModeKeys.TRAIN)

    # 这里可以添加更多的训练代码,例如定义模型、优化器等
    # 示例:简单打印读取的特征和标签
    with tf.Session() as sess:
        sess.run(tf.global_variables_initializer())
        sess.run(tf.local_variables_initializer())
        coord = tf.train.Coordinator()
        threads = tf.train.start_queue_runners(coord=coord)
        try:
            while not coord.should_stop():
                feats, lbl = sess.run([features, label])
                print("Features:", feats)
                print("Label:", lbl)
        except tf.errors.OutOfRangeError:
            print("End of training data.")
        finally:
            coord.request_stop()
        coord.join(threads)

3.7 代码解释

  • 导入必要的库 :导入 tensorflow argparse 库,分别用于机器学习和命令行参数解析。
  • 定义 CSV 列头和默认值 :明确 CSV 文件的列名和每列的默认值,用于后续的数据读取。
  • read_dataset 函数 :根据输入的文件名、模式、批量大小和训练轮数,读取数据集。在训练模式下,会多次读取数据集;在评估模式下,只读取一次。
  • 主函数 :使用 argparse 解析命令行参数,获取训练数据的文件名。调用 read_dataset 函数读取数据,并在会话中运行,打印读取的特征和标签。

4. 模型训练与评估

4.1 定义模型

在读取数据之后,需要定义一个模型来进行训练。以简单的逻辑回归模型为例,代码如下:

# 定义特征列
feature_columns = []
for col in CSV_COLUMNS:
    if col != LABEL_COLUMN:
        if isinstance(DEFAULTS[CSV_COLUMNS.index(col)][0], str):
            feature_columns.append(tf.feature_column.categorical_column_with_hash_bucket(col, hash_bucket_size=1000))
        else:
            feature_columns.append(tf.feature_column.numeric_column(col))

# 定义宽深模型
deep_columns = [tf.feature_column.embedding_column(c, dimension=8) for c in feature_columns if isinstance(c, tf.feature_column.CategoricalColumn)]
wide_columns = [c for c in feature_columns if isinstance(c, tf.feature_column.NumericColumn)]

model = tf.estimator.DNNLinearCombinedClassifier(
    linear_feature_columns=wide_columns,
    dnn_feature_columns=deep_columns,
    dnn_hidden_units=[10, 10]
)

4.2 训练模型

使用读取的数据训练模型:

train_spec = tf.estimator.TrainSpec(
    input_fn=lambda: read_dataset(args.traindata, mode=tf.contrib.learn.ModeKeys.TRAIN),
    max_steps=1000
)
eval_spec = tf.estimator.EvalSpec(
    input_fn=lambda: read_dataset(args.testdata, mode=tf.contrib.learn.ModeKeys.EVAL)
)

tf.estimator.train_and_evaluate(model, train_spec, eval_spec)

4.3 评估模型

训练完成后,评估模型的性能:

eval_result = model.evaluate(input_fn=lambda: read_dataset(args.testdata, mode=tf.contrib.learn.ModeKeys.EVAL))
print("Evaluation result:", eval_result)

4.4 模型训练与评估流程

下面是一个 mermaid 流程图,展示模型训练与评估的主要流程:

graph LR
    A[读取训练数据] --> B[定义模型]
    B --> C[训练模型]
    C --> D[评估模型]
    D --> E[输出评估结果]

4.5 模型训练与评估总结

步骤 操作 代码示例
定义模型 定义特征列和宽深模型 feature_columns = ...
model = tf.estimator.DNNLinearCombinedClassifier(...)
训练模型 使用训练数据训练模型 train_spec = tf.estimator.TrainSpec(...)
eval_spec = tf.estimator.EvalSpec(...)
tf.estimator.train_and_evaluate(model, train_spec, eval_spec)
评估模型 使用测试数据评估模型 eval_result = model.evaluate(input_fn=...)

通过以上步骤,完成了从数据处理、模型构建、数据读取到模型训练和评估的整个流程,为解决航班延误预测问题提供了一个完整的机器学习解决方案。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值