30、机器学习模型优化全解析

机器学习模型优化核心方法

机器学习模型优化全解析

在机器学习领域,为了提升模型性能,我们常常需要考虑使用更复杂的模型。下面就来详细探讨几种有效的模型优化方法。

深度神经网络模型

在TensorFlow playground的示例应用中,分类器的目标是将蓝色点和橙色点分开。输入为每个点的两个坐标X1和X2。第一个神经元计算X1和X2的加权和,ReLU神经元学习到的分离面是一条线,如图1所示。

graph LR
    A[输入X1和X2] --> B[第一个神经元计算加权和]
    B --> C[学习到一条线]

第二个ReLU神经元为输入参数学习不同的权重,即不同的线。单个神经元并不是很好的分类器,但隐藏层中有四个这样的神经元,它们的组合最终形成一个多边形,能够很好地对输入数据进行分类,如图2所示。

对于更复杂的布局,单个多边形可能不够,需要多层网络构建由多个多边形组成的分离面。增加网络的复杂度(如添加更多节点)有助于神经网络学习更复杂的形状,不过网络只是用多边形近似模式,学习范围受数据限制。

需要注意的是,前面的示例是针对二维输入的可视化,在实际问题中,输入更多,分离面和多边形的类比会失效,但理解隐藏层神经元组合形成分离超平面是有帮助的。

嵌入技术

逻辑回归可以看作是输入的加权和后接一个S形激活函数。为了引入更多自由参数并学习更复杂的分离面,可以引入隐藏层神经元。每个神经元计算输入的加权和并通过激活函数(如ReLU),不同神经元的权重不同,专门进行不同的线性分离,整体分类器是这些线性分离器的组合。

在神经网络中,直接使用实值数字没问题,但直接在深度神经网络中使用独热编码的稀疏列并不可取。例如,将“origin”变量离散化为1000个桶,独热编码会产生1000列的极稀疏矩阵。为避免列数爆炸,可对分类字段进行降维,即嵌入技术。

在TensorFlow中,可使用嵌入列对稀疏列进行降维,代码如下:

def create_embed(sparse_col):
    nbins = col.bucket_size
    dim = 1 + int(round(np.log2(nbins)))
    return tflayers.embedding_column(sparse_col, dimension=dim)

该代码将稀疏列嵌入到(1 + log2N)个值中,N是该列的桶数。例如,有1024个值的列将被量化为11个分量的向量,量化的权重由神经网络训练过程确定。

创建稀疏列的嵌入后,使用深度神经网络就很简单了。使用实值列,对所有稀疏列创建嵌入,然后从实值和嵌入的稀疏列创建深度神经网络,代码如下:

def dnn_model(output_dir):
    real, sparse = get_features()
    all = {}
    all.update(real)
    embed = {
        colname : create_embed(col) \
            for colname, col in sparse.items()
    }
    all.update(embed)
    estimator = tflearn.DNNClassifier(model_dir=output_dir,
                                      feature_columns=all.values(),
                                      hidden_units=[64, 16, 4])
    estimator.params["head"]._thresholds = [0.7]
    return estimator

该网络有三个隐藏层,分别有64、16和4个神经元,默认使用ReLU激活函数。关于层数和神经元数量,通常从近似输入节点数开始,后续层逐渐减少,但具体还需实验确定。

以下是使用嵌入技术创建稀疏列的实验结果:
| 实验编号 | 模型 | 特征 | RMSE |
| ---- | ---- | ---- | ---- |
| 1(方便重复) | 线性 | 所有输入原样 | 0.196 |
| 4 | 深度神经网络 | 所有输入变量,嵌入到log2N | 0.196 |

可以看到,单独使用深度神经网络并没有比逻辑回归模型有性能提升,这表明在机器学习中,数据工程比数据科学可能带来更大回报。

宽深模型

在放弃模型改进之前,可尝试宽深模型。该模型由两部分组成:一部分是将输入直接连接到输出的线性模型;另一部分是通过深度神经网络连接输入和输出。将稀疏列放在线性部分,实值列放在深度部分。

对于精度过高可能导致过拟合的实值列,可进行离散化并转换为分类列。还可对组合效果好的分类特征应用特征交叉,例如颜色和尺寸的特征交叉会产生颜色 - 尺寸组合的稀疏列。

以航班数据为例,出发和到达机场的纬度和经度本身对航班是否晚点影响不大,但机场位置和城市对之间的航线有影响。可将纬度和经度离散化并交叉桶,将国家划分为网格,代码如下:

latbuckets = np.linspace(20.0, 50.0, nbuckets).tolist()  # USA
lonbuckets = np.linspace(-120.0, -70.0, nbuckets).tolist() # USA
disc = {}
disc.update({
    'd_{}'.format(key) : tflayers.bucketized_column(real[key], latbuckets) \
        for key in ['dep_lat', 'arr_lat']
})
disc.update({
    'd_{}'.format(key) : tflayers.bucketized_column(real[key], lonbuckets) \
        for key in ['dep_lon', 'arr_lon']
})
sparse['dep_loc'] = tflayers.crossed_column([disc['d_dep_lat'], 
                          disc['d_dep_lon']], nbuckets*nbuckets)
sparse['arr_loc'] = tflayers.crossed_column([disc['d_arr_lat'],
                         disc['d_arr_lon']], nbuckets*nbuckets)
sparse['dep_arr'] = tflayers.crossed_column([sparse['dep_loc'],
                         sparse['arr_loc']], nbuckets ** 4)
sparse['ori_dest'] = tflayers.crossed_column([sparse['origin'],
                         Sparse['dest'], hash_bucket_size=1000)

对稀疏列创建嵌入并更新实值列后,创建宽深模型,代码如下:

estimator = \
    tflearn.DNNLinearCombinedClassifier(model_dir=output_dir,
                             linear_feature_columns=sparse.values(),
                             dnn_feature_columns=real.values(),
                             hidden_units=[64, 16, 4])

使用改进后的模型和额外的工程特征进行训练,能获得更好的性能,如下表所示:
| 实验编号 | 模型 | 特征 | RMSE |
| ---- | ---- | ---- | ---- |
| 1(方便重复) | 线性 | 所有输入原样 | 0.196 |
| 5 | 宽深模型 | 所有输入变量,特征交叉,嵌入 | 0.187 |

使用更合适的机器学习模型和反映人类对问题洞察的特征,有助于提高模型性能。

超参数调优

在模型中,有很多选择是随意的,如层数、隐藏节点数、批量大小、离散化的桶数等。这些选择会影响模型性能,过多或过少的层可能导致次优分类或过拟合,批量大小过大会降低优化器对特定数据点的敏感性,桶数选择不当会影响模型的区分能力或导致过拟合。

为了找到最佳参数组合,可使用Cloud ML Engine的非线性超参数调优功能。具体步骤如下:
1. 模型修改
- 添加超参数评估指标,例如:

eval_metrics = {
    'rmse' : tflearn.MetricSpec(metric_fn=my_rmse, prediction_key='probabilities'),
    'training/hptuning/metric' : tflearn.MetricSpec(metric_fn=my_rmse, prediction_key='probabilities')
}
- 更改输出目录,避免不同运行相互覆盖:
output_dir = os.path.join(
    output_dir,
    json.loads(
        os.environ.get('TF_CONFIG', '{}')
    ).get('task', {}).get('trial', '')
)
- 为每个超参数添加命令行参数,例如:
parser.add_argument(
    '--batch_size',
    help='Number of examples to compute gradient on',
    type=int,
    default=512
)
  1. 超参数配置文件
trainingInput:
  scaleTier: STANDARD_1
  hyperparameters:
    goal: MINIMIZE
    maxTrials: 50
    maxParallelTrials: 5
    params:
    - parameterName: batch_size
      type: INTEGER
      minValue: 16
      maxValue: 512
      scaleType: UNIT_LOG_SCALE
    - parameterName: nbuckets
      type: INTEGER
      minValue: 5
      maxValue: 10
      scaleType: UNIT_LINEAR_SCALE
    - parameterName: hidden_units
      type: CATEGORICAL
      categoricalValues: ["64,16", "64,16,4", "64,64,64,8", "256,64,16"]   

该文件指定了超参数的搜索空间,如批量大小在16到512之间以对数尺度搜索,桶数在5到10之间线性分布,隐藏单元指定了几个候选值。

  1. 运行超参数调优
    使用 gcloud 提交超参数调优作业,与提交训练作业类似,只是多了指向配置文件的参数:
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 \
  --config=hyperparam.yaml \
  -- \
  --output_dir=$OUTPUT_DIR \
  --traindata $DATA_DIR/train$PATTERN \
  --evaldata $DATA_DIR/test$PATTERN \
  --num_training_epochs=5

通过超参数调优,能找到更合适的参数组合,但要注意,虽然调优可能带来性能提升,但特征工程对模型性能的影响更为显著。例如,添加关键特征可将RMSE从0.25降至0.19,而添加额外层带来的RMSE改进不到0.01。

更改学习率

在超参数调优接近尾声时,可尝试更改学习率。损失函数的波动可能意味着需要降低学习率,同时,目前增加层仍有帮助,可尝试四、五、六层网络。批量大小可降至100,但不宜降至16,以免训练过慢且损失函数波动增大。

在使用Estimator API时,更改学习率并不容易,因为它屏蔽了底层细节。但可以将学习率作为命令行参数传递给创建模型的函数。 DNNLinearCombinedClassifier 使用两个优化器(FtrlOptimizer和AdagradOptimizer),为简单起见,可让深度列的学习率是宽列学习率的四分之一,代码如下:

def wide_and_deep_model(output_dir, 
                        nbuckets=5, hidden_units='64,32', learning_rate=0.01):
    ...
    estimator = \
        tflearn.DNNLinearCombinedClassifier(model_dir=output_dir,
               linear_feature_columns=sparse.values(),
               dnn_feature_columns=real.values(),
               dnn_hidden_units=parse_hidden_units(hidden_units),
               linear_optimizer=tf.train.FtrlOptimizer(
                             learning_rate=learning_rate),
               dnn_optimizer=tf.train.AdagradOptimizer(
                                learning_rate=learning_rate/4))

注意,要设置学习率,必须传入适当初始化的优化器对。

通过以上一系列的模型优化方法,从深度神经网络到宽深模型,再到超参数调优和学习率调整,我们可以逐步提升机器学习模型的性能,但在整个过程中,要始终重视特征工程的重要性。

机器学习模型优化全解析

超参数调优结果分析

在运行超参数调优后,会得到各种试验的输出结果。我们可以使用TensorFlow自带的TensorBoard工具来监控输出目录。例如,某次试验中,损失函数出现波动,尽管在小批量计算时有些波动是正常的,但即使是平滑后的曲线收敛也不稳定,这表明可能需要降低学习率。

以下是部分试验的结果:
| 试验编号 | 最终指标(RMSE) | 训练步数 | 超参数(批量大小,隐藏单元,桶数) |
| ---- | ---- | ---- | ---- |
| 1 | 0.202596 | 3559 | 170,64,16,6 |
| 2 | 0.186319 | 33637 | 16,64,64,64,8,10 |
| 18 | 0.187016 | 5097 | 100,64,16,4,5 |

从这些结果可以看出,不同的超参数组合对模型性能有不同的影响。最小的批量大小、最多的隐藏层和最大的桶数组合取得了较好的结果,但与更简单的网络模型相比,性能提升并不显著。这再次强调了特征工程的重要性,我们通过添加关键特征能够实现更大幅度的RMSE降低,而增加网络层数带来的提升相对较小。

深入理解模型组件

为了更好地优化模型,我们需要深入理解模型的各个组件。例如,在宽深模型中,线性部分和深度部分的作用不同。线性部分直接连接输入和输出,对于稀疏列的处理更为有效;深度部分通过神经网络能够学习到更复杂的模式。

下面是宽深模型的工作流程mermaid流程图:

graph LR
    A[输入数据] --> B[分离实值列和稀疏列]
    B --> C[实值列直接使用]
    B --> D[稀疏列进行嵌入处理]
    C --> E[构建深度部分]
    D --> E
    D --> F[构建线性部分]
    E --> G[DNN部分计算]
    F --> H[线性部分计算]
    G --> I[合并结果]
    H --> I
    I --> J[输出预测结果]

在这个流程中,输入数据首先被分离为实值列和稀疏列。实值列直接用于构建深度部分,稀疏列则先进行嵌入处理,一部分用于深度部分,一部分用于线性部分。最后,将两部分的计算结果合并得到最终的预测结果。

持续优化的思路

在完成一轮超参数调优和模型训练后,我们不能满足于当前的结果,而应该持续探索优化的可能性。以下是一些持续优化的思路:
1. 增加特征 :继续挖掘与问题相关的特征,例如在航班延误预测中,可以考虑天气数据、机场繁忙程度等因素。
2. 尝试不同的模型架构 :除了宽深模型,还可以尝试其他类型的神经网络模型,如卷积神经网络(CNN)或循环神经网络(RNN),根据问题的特点选择合适的模型。
3. 优化数据处理流程 :检查数据清洗、预处理等步骤,确保数据的质量和一致性。例如,处理缺失值、异常值等。
4. 调整超参数范围 :根据上一轮超参数调优的结果,缩小或扩大某些超参数的范围,进行更精细的调优。

总结与展望

在机器学习模型优化的过程中,我们涉及了多种方法,包括深度神经网络模型、嵌入技术、宽深模型、超参数调优等。这些方法相互配合,能够逐步提升模型的性能。但需要注意的是,特征工程始终是提升模型性能的关键因素,它能够为模型提供更有价值的信息。

未来,随着数据量的不断增加和计算能力的提升,我们可以尝试更复杂的模型和更精细的调优策略。同时,结合领域知识和人类的洞察力,能够更好地理解问题和优化模型。例如,在航班延误预测中,结合航空领域的专业知识,能够更准确地选择特征和调整模型。

总之,机器学习模型优化是一个持续的过程,需要不断地尝试和探索,以找到最适合问题的解决方案。通过本文介绍的方法和思路,希望能够为你在模型优化的道路上提供一些帮助。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值