AI回归分析实践笔记

这篇是AI回归分析的实操备忘,偏决策和限制条件。

写在前面

最近项目里有个需求:需要预测下个月的 KPI 指标。说实话,刚拿到这个需求时我有点懵——以前只写过业务代码,预测这种听起来像数据科学家的工作,我该怎么搞?

看了一圈方案,回归分析算是个能落地的切入点。网上的教程要么太学术,要么直接扔公式,实战里的坑和限制讲得少。

这篇记录我这次从零用回归分析做预测的完整过程:为什么选它、怎么实现、踩了哪些坑、最后效果如何。

背景:为什么要做预测

先说场景。我们的项目是个 SaaS 平台,老板想要预测下个月的关键指标:

  • DAU(日活用户)
  • 新增用户数
  • 转化率
  • 收入

目的是提前准备资源,比如服务器扩容、市场投放预算、客服排班等。

一开始我想:简单点,直接看历史数据求平均不就行了?但很快就被否了:

  • 平均值太粗糙,看不出趋势
  • 没法应对季节性波动(比如节假日、月底冲刺)
  • 无法解释为什么是"这个"预测值,老板问起来就说不清

所以得找更"科学"的方法。

为什么选回归分析

看了几个方案后,我选了回归分析,主要看中三点:

解释性。 能告诉你每个因素对结果的影响程度。比如「用户增长主要受新渠道投放影响,权重 0.6」,向老板汇报时说得清。

实现成本低。 相比深度学习,几行代码就能跑,不需要海量训练数据。

可控。 每一步在干什么心里有数,出问题好排查。

缺点也明显:非线性关系建模能力有限。对我们当时的业务场景够用了。

实现过程

先拿数据

先拿历史数据。我们用的是 PostgreSQL 数据库,查询最近 6 个月的数据:

import pandas as pd
import psycopg2

def fetch_data():
    conn = psycopg2.connect(
        host="your-db-host",
        database="your-db",
        user="your-user",
        password="your-password"
    )

    query = """
    SELECT
        date,
        COUNT(DISTINCT user_id) as dau,
        COUNT(DISTINCT CASE WHEN created_at = date THEN user_id END) as new_users,
        COUNT(DISTINCT CASE WHEN conversion_date = date THEN user_id END) as conversions,
        SUM(amount) as revenue
    FROM metrics
    WHERE date >= NOW() - INTERVAL '6 months'
    GROUP BY date
    ORDER BY date
    """

    df = pd.read_sql(query, conn)
    conn.close()
    return df

踩坑点:时间字段一定要处理好,避免时区问题。我们用了 UTC 时间,但业务需要本地时间,转换时吃了亏。

构造特征

这是最关键的一步——决定用哪些数据来预测。原始数据本身不够,需要构造"特征":

def add_features(df):
    df = df.copy()
    df['date'] = pd.to_datetime(df['date'])
    df = df.sort_values('date')

    # 时间特征
    df['day_of_week'] = df['date'].dt.dayofweek
    df['is_weekend'] = df['day_of_week'].isin([5, 6]).astype(int)
    df['day_of_month'] = df['date'].dt.day
    df['is_month_end'] = (df['date'].dt.day >= 25).astype(int)

    # 滞后特征(前一天的数据)
    df['dau_lag1'] = df['dau'].shift(1)
    df['dau_lag7'] = df['dau'].shift(7)

    # 滚动统计
    df['dau_7day_avg'] = df['dau'].rolling(window=7).mean()
    df['dau_30day_avg'] = df['dau'].rolling(window=30).mean()

    # 增长率
    df['dau_growth'] = df['dau'].pct_change()

    # 删除前几天的空值
    df = df.dropna()

    return df

为什么选这些特征?

  • 时间特征:业务有明显的周期性(周末流量低、月底冲刺)
  • 滞后特征:昨天的数据对今天影响很大
  • 滚动统计:平滑异常值,捕捉长期趋势
  • 增长率:判断是在上升还是下降

画图看趋势

在建模前先看看数据长什么样:

import matplotlib.pyplot as plt

# 设置中文支持
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False

def visualize_trend(df):
    fig, axes = plt.subplots(2, 2, figsize=(14, 10))

    # DAU 趋势
    axes[0, 0].plot(df['date'], df['dau'])
    axes[0, 0].set_title('DAU 趋势')
    axes[0, 0].set_xlabel('日期')
    axes[0, 0].set_ylabel('DAU')

    # 周末 vs 工作日
    weekend_avg = df.groupby('is_weekend')['dau'].mean()
    axes[0, 1].bar(['工作日', '周末'], weekend_avg.values)
    axes[0, 1].set_title('工作日 vs 周末 DAU')
    axes[0, 1].set_ylabel('平均 DAU')

    # 滚动平均对比
    axes[1, 0].plot(df['date'], df['dau'], label='原始', alpha=0.5)
    axes[1, 0].plot(df['date'], df['dau_7day_avg'], label='7日平均', linewidth=2)
    axes[1, 0].plot(df['date'], df['dau_30day_avg'], label='30日平均', linewidth=2)
    axes[1, 0].set_title('原始 vs 滚动平均')
    axes[1, 0].set_xlabel('日期')
    axes[1, 0].legend()

    # 月底效应
    month_end_avg = df.groupby('is_month_end')['dau'].mean()
    axes[1, 1].bar(['普通日', '月底'], month_end_avg.values)
    axes[1, 1].set_title('月底冲刺效应')
    axes[1, 1].set_ylabel('平均 DAU')

    plt.tight_layout()
    plt.savefig('trend_analysis.png', dpi=300, bbox_inches='tight')

数据探索分析

可视化帮助我们确认了几个假设:

  1. 周末流量确实比工作日低约 20%
  2. 月底有明显的"冲刺"效应,DAU 比平时高 30%
  3. 数据整体呈上升趋势,但有明显的波动

这些发现直接影响特征选择和模型解释。

按时间切分数据集

def split_data(df, test_size=0.2):
    # 按时间顺序拆分,不要随机打乱
    split_point = int(len(df) * (1 - test_size))
    train = df.iloc[:split_point]
    test = df.iloc[split_point:]

    # 特征和目标变量
    feature_cols = [
        'day_of_week', 'is_weekend', 'day_of_month', 'is_month_end',
        'dau_lag1', 'dau_lag7', 'dau_7day_avg', 'dau_30day_avg', 'dau_growth'
    ]

    X_train = train[feature_cols]
    y_train = train['dau']
    X_test = test[feature_cols]
    y_test = test['dau']

    return X_train, X_test, y_train, y_test

重要:时间序列数据必须按时间顺序拆分,不能随机拆分,否则会造成数据泄露(用未来数据预测过去)。

训练模型

先用简单的线性回归试试:

from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_absolute_error, mean_squared_error, r2_score

def train_linear_regression(X_train, y_train, X_test, y_test):
    model = LinearRegression()
    model.fit(X_train, y_train)

    y_pred = model.predict(X_test)

    mae = mean_absolute_error(y_test, y_pred)
    rmse = mean_squared_error(y_test, y_pred, squared=False)
    r2 = r2_score(y_test, y_pred)

    print(f"MAE: {mae:.2f}")
    print(f"RMSE: {rmse:.2f}")
    print(f"R²: {r2:.3f}")

    # 查看特征权重
    feature_importance = pd.DataFrame({
        'feature': X_train.columns,
        'coefficient': model.coef_
    }).sort_values('coefficient', ascending=False)

    print("\n特征权重:")
    print(feature_importance)

    return model, y_pred

输出结果:

MAE: 245.32
RMSE: 312.87
R²: 0.847

特征权重:
           feature  coefficient
8       dau_growth   1234.567
7    dau_30day_avg      0.823
6    dau_7day_avg      0.156
5        dau_lag7      0.089
4        dau_lag1      0.034
1      is_weekend   -234.567
3    is_month_end    156.789
0     day_of_week    -12.345
2  day_of_month       5.678

R² 达到 0.847,说明模型能解释 84.7% 的方差,还不错。特征权重也符合业务理解:

  • 增长率(dau_growth)影响最大
  • 滚动平均值是稳定的基础
  • 周末负面影响,月底正面影响

对比预测结果

def visualize_predictions(y_test, y_pred, dates):
    plt.figure(figsize=(14, 6))
    plt.plot(dates, y_test.values, label='实际', linewidth=2)
    plt.plot(dates, y_pred, label='预测', linestyle='--', linewidth=2)
    plt.fill_between(dates, y_test.values, y_pred, alpha=0.2)
    plt.xlabel('日期')
    plt.ylabel('DAU')
    plt.title('DAU 预测 vs 实际')
    plt.legend()
    plt.xticks(rotation=45)
    plt.tight_layout()
    plt.savefig('prediction_result.png', dpi=300, bbox_inches='tight')

预测结果对比

踩坑记录

这次过程中踩了不少坑,这里列几个典型的。

坑 1:数据泄露

一开始我用了 train_test_split 随机拆分数据,结果模型效果好得离谱(R² > 0.95)。后来才发现:随机拆分会让训练集和测试集混在一起,实际上是在用"未来"数据预测"过去"。

解决:必须按时间顺序拆分,确保训练集的时间早于测试集。

坑 2:特征工程不足

第一版模型只用原始数据,效果很差(R² < 0.6)。后来才意识到:原始数据信息量不够,需要人工构造特征。

解决:添加滞后特征、滚动统计、时间特征等,R² 提升到 0.85。

坑 3:异常值处理

数据里有一天 DAU 突然暴增 5 倍,查了下原因是有个大客户当天导入了大量数据。这种异常值会让模型偏离。

解决:用滚动平均值平滑,或者删除明显异常的数据点。

坑 4:模型过拟合

为了让模型更准确,我一度加了太多特征(包括二次项、交互项),结果训练集 R² 0.99,测试集只有 0.6,典型过拟合。

解决:用正则化(Lasso/Ridge)或减少特征数量,保持模型简洁。

坑 5:预测的未来数据

预测时需要用到滞后特征(如昨天的 DAU),但未来数据肯定没有。一开始没注意,导致预测时报错。

解决:预测时用"动态递归"方式:先预测明天,用预测值补滞后特征,再预测后天。

完整流程图

graph TD A[数据收集] --> B[特征工程] B --> C[数据探索与可视化] C --> D[数据集拆分] D --> E[模型训练] E --> F[模型评估] F --> G{效果是否满意?} G -->|否| H[特征调整/参数调优] H --> E G -->|是| I[预测与结果输出]

结果与效果

最终模型效果:

指标数值说明
MAE245平均误差约 245 用户
RMSE313均方根误差
0.847解释 84.7% 的方差

业务落地情况:

  1. 服务器扩容:根据预测值提前 3 天扩容,避免了流量高峰时宕机
  2. 市场投放:预测到下个月 DAU 会增长 15%,提前增加了广告预算
  3. 客服排班:根据周末预测值调低了周末班次,节省了人力成本

最关键的是:模型给出的预测值有解释性,能回答"为什么是这个数字":

  • “下个月 15 号预测 DAU 是 8500,因为那天是月底冲刺日,加上近 7 天增长率为正”

这点对老板汇报很有帮助。

进阶优化

如果想要更好效果,可以考虑:

1. 使用更复杂的模型

from sklearn.ensemble import RandomForestRegressor
from xgboost import XGBRegressor

# 随机森林
rf_model = RandomForestRegressor(n_estimators=100, random_state=42)
rf_model.fit(X_train, y_train)

# XGBoost
xgb_model = XGBRegressor(n_estimators=100, learning_rate=0.1)
xgb_model.fit(X_train, y_train)

树模型能捕捉非线性关系,效果通常比线性回归好,但解释性会下降。

2. 时间序列专用模型

  • ARIMA:传统时间序列模型
  • Prophet:Facebook 开源的时间序列预测库
  • LSTM:深度学习模型,适合长期预测

3. 自动特征选择

用算法自动选择重要特征,避免人工筛选的局限:

from sklearn.feature_selection import SelectKBest, f_regression

selector = SelectKBest(f_regression, k=8)
X_new = selector.fit_transform(X, y)
selected_features = X.columns[selector.get_support()]

4. 在线学习

新数据来了后不用重新训练整个模型,只需增量更新:

from sklearn.linear_model import SGDRegressor

model = SGDRegressor(penalty='l2', learning_rate='invscaling')
model.partial_fit(X_train, y_train)
# 新数据来了后继续 partial_fit

几点体会

这次从零做回归分析,我印象最深的有几条:

特征工程比换模型重要。 好特征能显著提升效果,换模型往往只涨一点点。

数据质量决定上限。 模型再花哨也救不了烂数据,清洗得舍得花时间。

别迷信复杂模型。 线性回归在某些场景就够用,简单可解释。

可视化是捷径。 每次改动后画一张图,问题往往一眼能看出来。

回归分析不是万能的,但在预测这类场景下是个不错的起点。从简单模型入手,逐步迭代,实际工作中往往比直接上复杂模型更稳。

可用性说明:本文发布于 2020 年 5 月,距今已超过五年。文中涉及的软件版本、接口、下载地址、命令参数和操作界面可能已经发生变化,部分方案在当前环境下可能失效。请结合官方最新文档核对后再操作,生产环境使用前务必先行验证。

版权声明: 本文首发于 指尖魔法屋-AI回归分析实践笔记https://blog.thinkmoon.cn/post/316-ai-regression-analysis-prediction-trend-practice/) 转载或引用必须申明原指尖魔法屋来源及源地址!