You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

在GCP机器学习引擎用词汇文件训练DNNClassifier及实现预测的最优方案

解决GCP ML Engine中DNNClassifier使用词汇文件的预测问题 & 最佳实践

我之前也碰到过一模一样的问题——训练时用GCS路径的词汇文件完全正常,但一到预测阶段就各种报错。核心原因在于tf.feature_column.categorical_column_with_vocabulary_file会在模型里记录原始词汇文件的路径,而云端预测的沙箱环境对GCS路径的处理逻辑和训练阶段不一样,导致无法读取外部文件。下面是更可靠的解决方案,附带完整的超参优化和云端预测示例:

核心优化思路

放弃直接用categorical_column_with_vocabulary_file指向GCS文件,改为提前把词汇表加载到内存,用tf.feature_column.categorical_column_with_vocabulary_list定义特征列。这样词汇表会被直接序列化到SavedModel中,预测时完全不需要依赖外部文件,彻底解决路径访问的问题。

完整示例代码

1. 兼容GCS的词汇表加载函数

import tensorflow as tf
from tensorflow.python.lib.io import file_io

def load_vocab(vocab_path):
    """加载GCS或本地路径的词汇表,返回去重后的词汇列表和大小"""
    with file_io.FileIO(vocab_path, 'r') as f:
        # 过滤空行并去重
        vocab_lines = list(set([line.strip() for line in f if line.strip()]))
    return vocab_lines, len(vocab_lines)

2. 带超参支持的模型构建逻辑

def build_estimator(model_dir, vocab_list, vocab_size, hidden_units=None, learning_rate=0.001):
    # 定义分类特征列(用内存中的词汇列表)
    categorical_col = tf.feature_column.categorical_column_with_vocabulary_list(
        key="target_feature",
        vocabulary_list=vocab_list,
        num_oov_buckets=1  # 预留一个桶处理词汇表外的未知值
    )
    # 转换为嵌入列(DNN只能处理数值型输入)
    embedding_col = tf.feature_column.embedding_column(
        categorical_column=categorical_col,
        dimension=vocab_size // 4  # 嵌入维度推荐设为词汇表大小的1/4~1/2
    )

    # 示例:添加数值特征列(根据你的实际数据调整)
    numeric_col = tf.feature_column.numeric_column(key="numeric_feature")

    # 构建DNNClassifier
    estimator = tf.estimator.DNNClassifier(
        feature_columns=[embedding_col, numeric_col],
        hidden_units=hidden_units or [128, 64],
        optimizer=tf.train.AdamOptimizer(learning_rate=learning_rate),
        n_classes=2,  # 根据你的分类任务调整类别数量
        model_dir=model_dir,
        dropout=0.2  # 添加 dropout 防止过拟合
    )
    return estimator

3. 训练、评估与模型导出逻辑

import argparse

def main(args):
    # 加载词汇表
    vocab_list, vocab_size = load_vocab(args.vocab_paths)

    # 解析超参(把字符串格式的hidden-units转为整数列表)
    hidden_units = [int(unit) for unit in args.hidden_units.split(",")]

    # 构建Estimator
    estimator = build_estimator(
        model_dir=args.job_dir,
        vocab_list=vocab_list,
        vocab_size=vocab_size,
        hidden_units=hidden_units,
        learning_rate=args.learning_rate
    )

    # 通用输入函数(支持训练/评估模式)
    def input_fn(file_path, batch_size=32, is_training=True):
        def parse_csv(line):
            # 根据你的数据格式调整解析规则
            cols = tf.decode_csv(line, record_defaults=[[""], [0.0], [0]])
            features = {
                "target_feature": cols[0],
                "numeric_feature": cols[1]
            }
            label = cols[2]
            return features, label

        dataset = tf.data.TextLineDataset(file_path)
        if is_training:
            dataset = dataset.shuffle(1000).repeat()
        dataset = dataset.map(parse_csv).batch(batch_size)
        return dataset

    # 启动训练
    estimator.train(
        input_fn=lambda: input_fn(args.train_files, is_training=True),
        steps=args.train_steps
    )

    # 模型评估
    eval_results = estimator.evaluate(
        input_fn=lambda: input_fn(args.eval_files, is_training=False),
        steps=args.eval_steps
    )
    print(f"评估结果: {eval_results}")

    # 导出SavedModel用于云端预测
    def serving_input_receiver_fn():
        # 定义预测时的输入格式(需与训练特征完全匹配)
        receiver_tensors = {
            "target_feature": tf.placeholder(tf.string, shape=[None], name="target_feature"),
            "numeric_feature": tf.placeholder(tf.float32, shape=[None], name="numeric_feature")
        }
        # 转换为模型可接受的格式
        feature_spec = {
            "target_feature": tf.FixedLenFeature([], tf.string),
            "numeric_feature": tf.FixedLenFeature([], tf.float32)
        }
        features = tf.parse_example(
            tf.serialize_many_from_tensor_dict(receiver_tensors),
            feature_spec
        )
        return tf.estimator.export.ServingInputReceiver(features, receiver_tensors)

    export_path = estimator.export_saved_model(
        export_dir_base=f"{args.job_dir}/export",
        serving_input_receiver_fn=serving_input_receiver_fn
    )
    print(f"模型已导出至: {export_path}")

if __name__ == "__main__":
    parser = argparse.ArgumentParser()
    # 基础必填参数
    parser.add_argument("--job-dir", required=True)
    parser.add_argument("--train-files", required=True)
    parser.add_argument("--eval-files", required=True)
    parser.add_argument("--vocab-paths", required=True)
    # 训练控制参数
    parser.add_argument("--train-steps", type=int, default=1000)
    parser.add_argument("--eval-steps", type=int, default=100)
    # 超参数(用于超参优化)
    parser.add_argument("--hidden-units", type=str, default="128,64")
    parser.add_argument("--learning-rate", type=float, default=0.001)
    args = parser.parse_args()
    main(args)

超参数优化配置

创建hptuning_config.yaml文件,定义超参搜索空间:

trainingInput:
  hyperparameters:
    goal: MAXIMIZE
    maxTrials: 10
    maxParallelTrials: 3
    hyperparameterMetricTag: accuracy
    params:
    - parameterName: hidden-units
      type: DISCRETE
      discreteValues:
      - "64,32"
      - "128,64"
      - "256,128,64"
    - parameterName: learning-rate
      type: DOUBLE
      minValue: 0.0001
      maxValue: 0.01
      scaleType: LOG

提交训练任务(含超参优化)

export JOB_NAME="dnn_classifier_hptune_$(date +%Y%m%d_%H%M%S)"
export MODEL_DIR="gs://your-bucket/models/$JOB_NAME"
export TRAIN_DATA="gs://your-bucket/data/train.csv"
export EVAL_DATA="gs://your-bucket/data/eval.csv"
export VOCAB="gs://your-bucket/data/vocabulary.txt"
export REGION="us-central1"

gcloud ml-engine jobs submit training $JOB_NAME \
  --job-dir $MODEL_DIR \
  --runtime-version 1.15 \  # 推荐用较新的稳定版本,1.4过于老旧
  --module-name trainer.task \
  --package-path trainer/ \
  --region $REGION \
  --config hptuning_config.yaml \  # 启用超参优化
  -- \
  --train-files $TRAIN_DATA \
  --eval-files $EVAL_DATA \
  --vocab-paths $VOCAB \
  --train-steps 2000 \
  --eval-steps 200

云端预测测试

1. 准备预测输入文件(input.json)

{"instances": [
  {"target_feature": "category_x", "numeric_feature": 0.7},
  {"target_feature": "category_y", "numeric_feature": 1.5}
]}

2. 提交预测请求

gcloud ml-engine predict \
  --model=your_model_name \
  --version=your_version_name \
  --json-request=input.json

为什么之前的方法预测失败?

当你使用categorical_column_with_vocabulary_file时,TensorFlow会在模型中记录词汇文件的路径,而非词汇表内容。在云端预测时,预测服务会尝试从该路径读取文件,但存在两个关键问题:

  • 预测沙箱的GCS访问权限与训练环境不一致
  • 旧版本TensorFlow(如1.4)对GCS路径的支持不完善

改用categorical_column_with_vocabulary_list后,词汇表直接嵌入到模型中,完全消除了外部文件依赖,是GCP ML Engine上使用分类特征的更可靠方案。

内容的提问来源于stack exchange,提问作者Quintin Sheridan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.28 10:04:17