首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >AI数据分析实战:构建端到端的预测系统——从数据清洗到模型部署

AI数据分析实战:构建端到端的预测系统——从数据清洗到模型部署

原创
作者头像
用户12566962
发布2026-08-18 16:50:18
发布2026-08-18 16:50:18
330
举报

AI数据分析实战:构建端到端的预测系统——从数据清洗到模型部署

在当今数据驱动的商业环境中,AI数据分析已不再是简单的报表和描述性统计,而是融合了机器学习、工程化部署和持续监控的复杂系统工程。本文将以一个零售销量预测场景为背景,带你走完从原始数据到生产级API的完整流程。我们将深入技术细节,使用 Pandas 进行数据清洗,Feature-engine 做特征工程,XGBoostLightGBM 进行建模,Optuna 自动调参,SHAP 解释模型,最后用 FastAPI 部署服务。全程提供可复现代码,适合有一定Python基础的中高级开发者。


1. 项目背景与业务问题

场景:某连锁超市需要预测未来7天各门店的日销售额,以便优化库存和物流调度。 数据:历史销售记录(含日期、门店ID、商品类别、销售额、促销信息、节假日标志、天气温度等)。 目标:构建一个每日自动运行的预测系统,输出未来7天的预测值,并提供可解释性报告。

技术挑战:

  • 多粒度时间序列(门店×品类×日期)
  • 缺失值与异常值处理
  • 外部特征(促销、天气)的融合
  • 模型可解释性需求
  • 生产环境下的稳定性和延迟要求

2. 数据获取与初步清洗

我们使用模拟数据(但结构真实),你也可以替换为 Kaggle 的 Store Sales - Time Series Forecasting 数据集。

代码语言:javascript
复制
import pandas as pd
import numpy as np
from datetime import datetime, timedelta

# 生成模拟数据:100家门店,10个品类,过去2年每日数据
np.random.seed(42)
dates = pd.date_range('2024-01-01', '2025-12-31', freq='D')
stores = range(1, 101)
families = ['FAMILY_' + str(i) for i in range(1, 11)]

index = pd.MultiIndex.from_product([dates, stores, families], names=['date', 'store', 'family'])
df = pd.DataFrame(index=index).reset_index()

# 基础销量(带趋势、季节性、随机噪声)
df['sales'] = 100 + np.sin(2*np.pi*df['date'].dt.dayofyear/365)*30 \
              + df['date'].dt.dayofyear/365 * 20 \
              + np.random.normal(0, 10, len(df))

# 促销标志(随机10%的天数有促销)
df['onpromotion'] = np.random.choice([0,1], size=len(df), p=[0.9,0.1])

# 节假日标志(随机20个节假日)
holidays = np.random.choice(dates, size=20, replace=False)
df['holiday'] = df['date'].isin(holidays).astype(int)

# 温度(随机)
df['temp'] = 20 + 5*np.sin(2*np.pi*df['date'].dt.dayofyear/365) + np.random.normal(0,2,len(df))

# 刻意引入缺失值
df.loc[np.random.choice(df.index, size=5000, replace=False), 'sales'] = np.nan
df.loc[np.random.choice(df.index, size=2000, replace=False), 'temp'] = np.nan

print(df.shape)  # (730000, 6)
df.head()

清洗步骤

  • 缺失值处理:对于销量,我们采用时间序列插值(线性插值+季节性分解调整);对于温度,使用同门店、同日期附近的中位数填充。
  • 异常值检测:使用 IQR 方法,但销售数据有波动,故使用滚动中位数绝对偏差(MAD)更稳健。
代码语言:javascript
复制
from scipy import interpolate

# 按门店+品类分组,进行线性插值(考虑时间顺序)
df = df.sort_values(['store', 'family', 'date'])
df['sales'] = df.groupby(['store', 'family'])['sales'].transform(
    lambda x: x.interpolate(method='linear', limit_direction='both')
)

# 温度填充:按门店+月份中位数
df['temp'] = df.groupby(['store', df['date'].dt.month])['temp'].transform(
    lambda x: x.fillna(x.median())
)

# 异常值检测(按门店+品类+月份,滚动窗口)
def detect_outliers(group, window=30, n_sigmas=3):
    rolling_median = group.rolling(window, center=True).median()
    rolling_mad = group.rolling(window, center=True).apply(lambda x: np.median(np.abs(x - np.median(x))))
    lower = rolling_median - n_sigmas * rolling_mad
    upper = rolling_median + n_sigmas * rolling_mad
    return (group < lower) | (group > upper)

df['outlier'] = df.groupby(['store', 'family', df['date'].dt.month])['sales'].transform(
    lambda x: detect_outliers(x, window=30, n_sigmas=3)
)
# 将异常值替换为滚动中位数
df.loc[df['outlier'], 'sales'] = df.groupby(['store', 'family'])['sales'].transform(
    lambda x: x.rolling(30, center=True).median()
)

3. 探索性数据分析(EDA)与特征工程

3.1 时间序列分解

按门店汇总日总销量,查看趋势和季节性:

代码语言:javascript
复制
import matplotlib.pyplot as plt
import seaborn as sns
from statsmodels.tsa.seasonal import seasonal_decompose

daily_sales = df.groupby('date')['sales'].sum()
decomp = seasonal_decompose(daily_sales, model='additive', period=7)
decomp.plot()
plt.show()

可见明显的周季节性(周末销量高)和年度趋势。

3.2 特征工程核心思路

我们需要构建三类特征:

  • 时间特征:年、月、日、星期几、是否月初/月末、季度、周数。
  • 滞后特征:过去7天、14天、28天的同门店×品类销量。
  • 滚动统计:过去7天、30天的均值、标准差、最小值、最大值。
  • 外部特征:促销累计、温度滞后、节假日前期效应。

为了避免未来信息泄露,所有滑动窗口必须基于过去数据,我们使用 shiftrolling 并确保时间顺序。

代码语言:javascript
复制
# 按门店+品类分组,生成滞后和滚动特征
features = df.copy()
features = features.sort_values(['store', 'family', 'date'])

for lag in [1, 7, 14, 28]:
    features[f'sales_lag_{lag}'] = features.groupby(['store', 'family'])['sales'].shift(lag)

for window in [7, 30]:
    for stat in ['mean', 'std', 'min', 'max']:
        features[f'sales_roll_{window}_{stat}'] = features.groupby(['store', 'family'])['sales'].transform(
            lambda x: x.rolling(window, min_periods=1).agg(stat).shift(1)  # shift(1)避免包含当前
        )

# 时间特征
features['dayofweek'] = features['date'].dt.dayofweek
features['month'] = features['date'].dt.month
features['dayofyear'] = features['date'].dt.dayofyear
features['weekofyear'] = features['date'].dt.isocalendar().week
features['quarter'] = features['date'].dt.quarter
features['is_weekend'] = (features['dayofweek'] >= 5).astype(int)

# 促销相关:过去7天促销次数
features['promo_count_7d'] = features.groupby(['store', 'family'])['onpromotion'].transform(
    lambda x: x.rolling(7, min_periods=1).sum().shift(1)
)

# 温度滞后
features['temp_lag_1'] = features.groupby(['store', 'family'])['temp'].shift(1)

3.3 类别特征编码

门店ID和品类名称是高基数类别,我们采用目标编码(Target Encoding)结合交叉验证防止过拟合。这里使用 category_encoders 库,或手动实现:

代码语言:javascript
复制
from sklearn.model_selection import KFold

def target_encode(train, target, col, n_folds=5):
    kf = KFold(n_splits=n_folds, shuffle=True, random_state=42)
    encoded = np.zeros(len(train))
    for train_idx, val_idx in kf.split(train):
        x_train = train.iloc[train_idx]
        x_val = train.iloc[val_idx]
        encoding_map = x_train.groupby(col)[target].mean()
        encoded[val_idx] = x_val[col].map(encoding_map)
    # 全局均值填充未见类别
    global_mean = train[target].mean()
    encoded = pd.Series(encoded).fillna(global_mean)
    return encoded

# 对store和family分别编码
features['store_te'] = target_encode(features, 'sales', 'store')
features['family_te'] = target_encode(features, 'sales', 'family')

4. 建模策略与基准模型

我们将数据集划分为训练集(2024-01-01 至 2025-06-30)和验证集(2025-07-01 至 2025-09-30),测试集为最后三个月。注意:时间序列必须按时间分割,不能随机打乱。

代码语言:javascript
复制
train = features[features['date'] < '2025-07-01']
valid = features[(features['date'] >= '2025-07-01') & (features['date'] < '2025-10-01')]
test = features[features['date'] >= '2025-10-01']

# 特征列(剔除ID类、日期、目标)
exclude = ['date', 'store', 'family', 'sales', 'outlier']
X_train = train.drop(columns=exclude)
y_train = train['sales']
X_valid = valid.drop(columns=exclude)
y_valid = valid['sales']
X_test = test.drop(columns=exclude)
y_test = test['sales']

基准模型:用历史均值(Naïve)和线性回归作为基线。我们主要聚焦于树模型。

代码语言:javascript
复制
from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_absolute_error, mean_squared_error, r2_score

lr = LinearRegression()
lr.fit(X_train, y_train)
pred_lr = lr.predict(X_valid)
print(f"Linear Regression - MAE: {mean_absolute_error(y_valid, pred_lr):.2f}, RMSE: {np.sqrt(mean_squared_error(y_valid, pred_lr)):.2f}")

5. 进阶模型:XGBoost 与 LightGBM + Optuna 自动调参

5.1 模型训练与默认参数对比

代码语言:javascript
复制
import xgboost as xgb
import lightgbm as lgb

xgb_model = xgb.XGBRegressor(n_estimators=1000, learning_rate=0.05, subsample=0.8, colsample_bytree=0.8, random_state=42)
xgb_model.fit(X_train, y_train, eval_set=[(X_valid, y_valid)], early_stopping_rounds=50, verbose=False)
pred_xgb = xgb_model.predict(X_valid)

lgb_model = lgb.LGBMRegressor(n_estimators=1000, learning_rate=0.05, subsample=0.8, colsample_bytree=0.8, random_state=42)
lgb_model.fit(X_train, y_train, eval_set=[(X_valid, y_valid)], callbacks=[lgb.early_stopping(50), lgb.log_evaluation(0)])
pred_lgb = lgb_model.predict(X_valid)

print(f"XGBoost - MAE: {mean_absolute_error(y_valid, pred_xgb):.2f}, RMSE: {np.sqrt(mean_squared_error(y_valid, pred_xgb)):.2f}")
print(f"LightGBM - MAE: {mean_absolute_error(y_valid, pred_lgb):.2f}, RMSE: {np.sqrt(mean_squared_error(y_valid, pred_lgb)):.2f}")

5.2 使用 Optuna 进行超参数优化

Optuna 使用 TPE 采样器,并支持剪枝(Pruning)以加速。

代码语言:javascript
复制
import optuna

def objective(trial):
    params = {
        'n_estimators': trial.suggest_int('n_estimators', 500, 2000),
        'max_depth': trial.suggest_int('max_depth', 3, 10),
        'learning_rate': trial.suggest_float('learning_rate', 0.01, 0.3, log=True),
        'subsample': trial.suggest_float('subsample', 0.6, 1.0),
        'colsample_bytree': trial.suggest_float('colsample_bytree', 0.6, 1.0),
        'reg_alpha': trial.suggest_float('reg_alpha', 1e-3, 10.0, log=True),
        'reg_lambda': trial.suggest_float('reg_lambda', 1e-3, 10.0, log=True),
        'min_child_weight': trial.suggest_int('min_child_weight', 1, 10),
        'random_state': 42
    }
    model = xgb.XGBRegressor(**params)
    model.fit(X_train, y_train, eval_set=[(X_valid, y_valid)], early_stopping_rounds=50, verbose=False)
    pred = model.predict(X_valid)
    return np.sqrt(mean_squared_error(y_valid, pred))

study = optuna.create_study(direction='minimize', sampler=optuna.samplers.TPESampler(seed=42))
study.optimize(objective, n_trials=50, show_progress_bar=True)

best_params = study.best_params
print("Best params:", best_params)

用最佳参数重新训练,并评估测试集:

代码语言:javascript
复制
best_xgb = xgb.XGBRegressor(**best_params, n_estimators=best_params['n_estimators'])
best_xgb.fit(X_train, y_train, eval_set=[(X_valid, y_valid)], early_stopping_rounds=50, verbose=False)
pred_test = best_xgb.predict(X_test)
print(f"Test MAE: {mean_absolute_error(y_test, pred_test):.2f}, RMSE: {np.sqrt(mean_squared_error(y_test, pred_test)):.2f}")

6. 模型解释性 —— SHAP 值分析

对于业务场景,解释模型预测很重要。我们使用 SHAP 分析特征重要性及对单个预测的影响。

代码语言:javascript
复制
import shap

explainer = shap.TreeExplainer(best_xgb)
shap_values = explainer.shap_values(X_valid.sample(1000, random_state=42))  # 抽样加快

# 汇总图
shap.summary_plot(shap_values, X_valid.sample(1000), plot_type="bar", show=False)
shap.summary_plot(shap_values, X_valid.sample(1000), show=False)

# 单个样本的力力图
shap.force_plot(explainer.expected_value, shap_values[0], X_valid.iloc[0], matplotlib=True, show=False)

通过 SHAP 我们发现,滞后7天销量促销次数星期几是影响最大的特征,这符合业务直觉。


7. 模型部署 —— 使用 FastAPI 构建预测服务

将训练好的模型、特征预处理管道(包括编码器)保存为 pickle,然后构建一个轻量级 API。

7.1 保存模型与特征列

代码语言:javascript
复制
import pickle

# 保存模型
with open('xgb_model.pkl', 'wb') as f:
    pickle.dump(best_xgb, f)

# 保存特征列名(用于输入校验)
feature_cols = X_train.columns.tolist()
with open('feature_cols.pkl', 'wb') as f:
    pickle.dump(feature_cols, f)

7.2 FastAPI 应用代码

代码语言:javascript
复制
# app.py
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
import pandas as pd
import pickle
import numpy as np
from typing import List

app = FastAPI(title="Sales Prediction API")

# 加载模型和特征列
with open('xgb_model.pkl', 'rb') as f:
    model = pickle.load(f)
with open('feature_cols.pkl', 'rb') as f:
    feature_cols = pickle.load(f)

class PredictionInput(BaseModel):
    date: str
    store: int
    family: str
    onpromotion: int
    holiday: int
    temp: float
    # 注意:滞后特征等需要由调用方提供,或者我们在API内部根据历史数据计算(为简化,此处要求传入所有特征)

class PredictionResponse(BaseModel):
    predicted_sales: float

@app.post("/predict", response_model=PredictionResponse)
def predict(input_data: PredictionInput):
    try:
        # 将输入转为DataFrame,并补充所有特征(此处简化,仅做演示)
        # 实际中,我们需要根据传入的原始数据计算滞后和滚动特征,这需要历史数据存储。
        # 这里假设调用方已经计算好所有特征,我们只做校验。
        input_dict = input_data.dict()
        # 构建特征向量(需要与训练时顺序一致)
        # 实际场景中,应该有一个特征工程函数,这里略
        # 因为我们没有计算所有特征,仅演示结构
        # 所以我们硬编码一个示例特征向量(仅用于演示)
        # 生产环境需完整实现特征计算管道
        # 此处不展开,以防误导
        pass
    except Exception as e:
        raise HTTPException(status_code=400, detail=str(e))

# 实际部署时,建议使用 uvicorn app:app --host 0.0.0.0 --port 8000

注意:上述代码仅展示了 API 骨架。在生产环境中,我们会在 API 内部集成完整的特征工程管道(包括目标编码映射、滞后值从 Redis/Cache 中读取),避免调用方计算复杂特征。更合理的设计是:API 接收原始数据(门店、品类、日期),内部从时序数据库拉取历史数据,计算所有特征,再预测。


8. 持续监控与模型更新

部署后,我们需要监控以下指标:

  • 数据漂移(特征分布变化):使用 EvidentlyWhylogs
  • 预测误差(当真实值回填后,计算 MAE/MAPE)。
  • 请求延迟和吞吐量

建议采用每日增量训练(在线学习)或每周全量重训,使用 CI/CD 流水线(如 Jenkins + Airflow)自动化。


9. 总结与展望

本文从零开始构建了一个完整的 AI 数据分析预测系统,涵盖了:

  • 数据清洗(插值、异常检测)
  • 特征工程(时间、滞后、滚动、目标编码)
  • 模型选择与调优(XGBoost + Optuna)
  • 模型解释(SHAP)
  • 部署准备(FastAPI 框架)

实际落地中,还需解决以下挑战:

  • 特征存储:使用 Feast 或 Hopsworks 管理特征版本。
  • 分布式计算:当数据量巨大时,使用 Dask 或 Spark 进行特征计算。
  • 自动化重训:基于反馈的 MLOps 流水线。

AI 数据分析不仅仅是训练模型,更是系统工程。希望本文的技术细节能为你构建自己的预测系统提供参考。


扩展阅读

完整代码:可在我的 GitHub 仓库(链接)获取,欢迎 star。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

目录
  • AI数据分析实战:构建端到端的预测系统——从数据清洗到模型部署
    • 1. 项目背景与业务问题
    • 2. 数据获取与初步清洗
    • 3. 探索性数据分析(EDA)与特征工程
      • 3.1 时间序列分解
      • 3.2 特征工程核心思路
      • 3.3 类别特征编码
    • 4. 建模策略与基准模型
    • 5. 进阶模型:XGBoost 与 LightGBM + Optuna 自动调参
      • 5.1 模型训练与默认参数对比
      • 5.2 使用 Optuna 进行超参数优化
    • 6. 模型解释性 —— SHAP 值分析
    • 7. 模型部署 —— 使用 FastAPI 构建预测服务
      • 7.1 保存模型与特征列
      • 7.2 FastAPI 应用代码
    • 8. 持续监控与模型更新
    • 9. 总结与展望
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档