出版社物流WMS智能调度实战(二):从“卡死”到“跑通”——机器学习开发排坑记

📖 本系列文章

📌 前置阅读
本文是系列第二篇。建议先阅读环境准备篇完成服务器搭建,以及第一篇了解整体架构与表设计。


摘要

在出版行业WMS智能调度项目中,我们搭建了一套基于LightGBM的销量预测模型。然而,在实际开发落地过程中,从特征工程、模型训练到预测评估、调度部署,每一步都踩了无数“坑”:变量缺失、表不存在、日期类型错误、ORA-00942、DPI-1015数组溢出、训练集为空、MAPE爆炸……本文逐一记录这些问题的现象、原因及解决方法,并总结出可复用的排查思路。读完本文,你将学会如何快速定位并解决类似问题,让机器学习项目真正跑起来。


一、背景与目标

在系列第一篇中,我们设计了双Oracle架构、销售/库存日汇总表、托盘任务池和6条出库口决策规则。本文将聚焦机器学习模型的开发落地,目标是通过销量预测为出库口决策提供依据。技术栈:Oracle 11g(远程备库)+ Python 3.9 + LightGBM + 报表库本地表。

整体流程

  1. 特征工程:从销售日汇总表和库存月末快照表构建特征表 WMS_ML_FEATURES
  2. 模型训练:训练托数和件数两个回归模型。
  3. 预测:为下个月生成销量预测,写入 WMS_ML_FORECAST_TUO
  4. 评估:对比预测与实际销量,计算误差指标。

然而,在一天之内,我们先后遇到了十几个报错,几乎每个环节都卡住。以下是详细的排坑记录。


二、问题与解决逐条记录

阶段一:特征工程(feature_engineering.py

问题1:ImportError: cannot import name 'PROGRESS_INTERVAL' from 'config'
  • 现象:运行 python3 feature_engineering.py 时,提示缺少 PROGRESS_INTERVAL
  • 原因config.py 中没有定义特征工程进度输出间隔和历史窗口天数。
  • 解决:在 config.py 末尾添加:
    PROGRESS_INTERVAL = 5000
    HISTORY_WINDOW = 365
    
问题2:ORA-00942: table or view does not existLOCAL_ITEM_ATTR 表缺失)
  • 现象:执行 fetch_item_attributes() 时,提示表不存在。
  • 原因:特征工程需要读取商品属性(每托件数、分类、首次上架日期等),但本地表 LOCAL_ITEM_ATTR 未创建。
  • 解决:创建表 WMS_ITEM_ATTR(或原名 LOCAL_ITEM_ATTR),并每日从远程库同步数据。关键字段:item_id, supportcode_quantity, class4, first_sale_date
问题3:AttributeError: 'numpy.datetime64' object has no attribute 'month'
  • 现象:在 add_labels() 函数中,month_start.month 报错。
  • 原因feature_df['begin_month'].unique() 返回的是 numpy.datetime64 类型,不能直接使用 .month 属性。
  • 解决:在循环开始时转换类型:
    month_start = pd.Timestamp(month)
    
    并手动计算 next_month(避免使用自定义的 add_months 函数)。
问题4:DPI-1015: array size of 264601 is too large
  • 现象:写入特征表时,df.to_sql 抛出 oracledb.exceptions.DatabaseError
  • 原因:pandas 默认一次性提交全部数据(26万行),超出 Oracle 数组大小限制。
  • 解决:修改 db_utils.py 中的 to_sql 方法,增加 chunksize=1000 分批写入,并指定 dtype 映射避免字符串列被误转为 CLOB
    def to_sql(self, df, table, if_exists='append', index=False, chunksize=1000):
        dtype = {col: types.VARCHAR(4000) for col in df.select_dtypes('object').columns}
        df.to_sql(table.upper(), self.engine, if_exists=if_exists,
                  index=index, chunksize=chunksize, dtype=dtype)
    
问题5:SettingWithCopyWarning 警告
  • 现象A value is trying to be set on a copy of a slice from a DataFrame.
  • 原因X = feature_df[ALL_FEATURES] 返回的是视图,修改视图中的列会触发警告。
  • 解决:改为深拷贝 X = feature_df[ALL_FEATURES].copy(),或使用 .loc 赋值。

阶段二:模型训练(train_model.py

问题6:ImportError: cannot import name 'LABEL_COL' from 'config'
  • 现象:训练脚本无法导入标签列名。
  • 原因config.py 中标签列定义为 LABEL_TUOLABEL_QTY,而脚本试图导入 LABEL_COL
  • 解决:修改导入,分别使用两个标签,并调整后续代码。
问题7:训练集为空(Train size: 0, Test size: 114
  • 现象ValueError: Input data must be 2 dimensional and non empty.
  • 原因:特征表只有4个月数据,而 prepare_train_test 默认最后3个月作为测试集,导致训练集为空。
  • 解决:将 test_months 改为1,或扩大特征表时间范围。暂时修改调用参数:prepare_train_test(df, test_months=1)
问题8:ValueError: DataFrame.dtypes for data must be int, float or bool
  • 现象Did not expect the data types in the following fields: publisher_code, category_code
  • 原因:LightGBM 要求特征全为数值类型,但类别列是字符串。
  • 解决:在 load_data() 中将类别列转为 category 类型:
    for col in CATEGORICAL_FEATURES:
        if col in df.columns:
            df[col] = df[col].astype('category')
    
问题9:FileNotFoundError: No such file or directory: './models/monthly_sales_predictor_tuo.lgb'
  • 现象:模型训练成功后保存文件时报错。
  • 原因models 目录不存在。
  • 解决mkdir -p /home/xinhua/wms_ml/models

阶段三:预测(predict.py

问题10:cannot import name 'MODEL_PATH' from 'config'
  • 现象:预测脚本无法导入模型路径。
  • 原因config.py 中定义了 MODEL_PATH_TUOMODEL_PATH_QTY,而非单一的 MODEL_PATH
  • 解决:分别导入并加载两个模型。
问题11:预测月份不合理
  • 现象:脚本使用 add_months(datetime.today().replace(day=1), 1) 计算下个月,但例如在5月初预测6月时,需要5月的销售历史(尚未发生),导致特征依赖未来数据。
  • 原因:业务上应预测下一个自然月,但当前时间点所需的历史数据尚未产生。
  • 解决:手动将目标月份改为已知有数据的月份(如 datetime(2025, 5, 1)),或根据当前日期动态调整:若当前日期早于当月10日,则预测当月;否则预测下月,并确保历史数据完整。

阶段四:评估(evaluate_model.py

问题12:MAPE 爆炸(837316302366179968.0%
  • 现象:评估输出的 MAPE 异常巨大。
  • 原因:大量 SKU 实际销量为 0,模型预测值非零,导致百分比无限大。
  • 解决:过滤实际销量小于阈值(如 min_actual_tuo=0.5)的样本,并增加对称平均绝对百分比误差(SMAPE)指标。
    def smape(y_true, y_pred):
        denominator = (np.abs(y_true) + np.abs(y_pred)) / 2
        diff = np.abs(y_true - y_pred) / denominator
        diff[denominator == 0] = 0
        return np.mean(diff) * 100
    
问题13:NameError: name 'np' is not defined
  • 现象smape 函数中使用 np 但未导入。
  • 解决:在脚本开头添加 import numpy as np

阶段五:部署与调度(crontab + Python调用存储过程)

问题14:ORA-06550: PLS-00103: Encountered the symbol ":"
  • 现象run_sales_daily.py 调用存储过程时报错。
  • 原因:使用 execute("BEGIN sp_backfill_wms_sales_hist(:start, :end); END;", params) 方式,Oracle 将其当作动态 SQL 解析,绑定变量语法错误。
  • 解决:在 db_utils.py 中添加 callproc 方法,改用 cursor.callproc() 直接调用。
    def callproc(self, proc_name, params):
        with self.engine.connect() as conn:
            cursor = conn.connection.cursor()
            result = cursor.callproc(proc_name, params)
            conn.commit()
            return result
    
    然后在 run_sales_daily.py 中调用:
    target_db.callproc('sp_backfill_wms_sales_hist', [yesterday, yesterday])
    
问题15:crontab 任务不执行 / 环境变量缺失
  • 现象:手动运行脚本成功,crontab 定时任务无日志、无效果。
  • 原因:crontab 默认 PATH 较窄,且不加载用户环境变量。
  • 解决
    • 在 crontab 顶部指定 PATH:PATH=/usr/local/bin:/usr/bin:/bin
    • 脚本中使用虚拟环境的绝对路径:/opt/wms_ml/venv/bin/python
    • 在脚本中 import os; os.chdir('/opt/wms_ml') 确保工作目录正确。

三、关键经验与教训

类别 经验
配置文件 所有常量(表名、路径、窗口大小、阈值)集中定义在 config.py,避免散落各处。
表存在性 每次运行前自动校验关键表和字段,缺失即终止并告警。
数据类型 numpy.datetime64datetime 需显式转换;LightGBM 类别特征用 category 类型。
批量写入 Oracle 必须使用 chunksize,否则 DPI-1015
评估指标 当实际销量有大量0值时,用 SMAPE 而非 MAPE。
历史库存 库存特征是销量预测的基石,缺失时要尽早推动回填或使用替代数据(如收发存流水重建)。
调用存储过程 必须用 callproc,不要用 execute + 绑定变量。
crontab 使用绝对路径,手动设置 PATH 和工作目录。

四、后续优化方向

  1. 补充历史库存数据:从收发存流水重建往年库存快照。
  2. 扩大训练集:构建足量历史特征,使标签非零样本从初期的小规模增至数万条。
  3. 超参数调优:使用 Optuna 或网格搜索优化 num_leaves, learning_rate 等。
  4. 特征增强:增加节假日标识、天气、区域经济指标等外部数据。
  5. 全自动调度:将特征工程、训练、预测、监控全部纳入 Airflow DAG。
  6. 模型版本管理:使用 MLflow 记录每次训练的模型参数、特征列表、评估指标。

五、结语

从“卡死”到“跑通”,我们花了整整一天时间解决开发环境的十几个报错。每一次错误都是对系统理解的加深,每一次优化都让模型更贴近业务。

希望这篇文章能帮助后来的同学少走弯路。记住:跑通流程只是第一步,持续监控与自动回滚才是生产级机器学习的护城河

📌 下一篇预告
本文是系列第二篇。系列第三篇《从“卡死”到“跑稳”:WMS机器学习运维监控与自动回滚实战》将深入讲解数据漂移检测、特征失效应对、蓝绿部署与自动回滚机制,敬请期待。

如果你也在 WMS 中跑机器学习,欢迎交流。下一篇见!

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐