在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
相关产品推荐
相关产品推荐

