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')

可视化帮助我们确认了几个假设:
- 周末流量确实比工作日低约 20%
- 月底有明显的"冲刺"效应,DAU 比平时高 30%
- 数据整体呈上升趋势,但有明显的波动
这些发现直接影响特征选择和模型解释。
按时间切分数据集
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),但未来数据肯定没有。一开始没注意,导致预测时报错。
解决:预测时用"动态递归"方式:先预测明天,用预测值补滞后特征,再预测后天。
完整流程图
结果与效果
最终模型效果:
| 指标 | 数值 | 说明 |
|---|---|---|
| MAE | 245 | 平均误差约 245 用户 |
| RMSE | 313 | 均方根误差 |
| R² | 0.847 | 解释 84.7% 的方差 |
业务落地情况:
- 服务器扩容:根据预测值提前 3 天扩容,避免了流量高峰时宕机
- 市场投放:预测到下个月 DAU 会增长 15%,提前增加了广告预算
- 客服排班:根据周末预测值调低了周末班次,节省了人力成本
最关键的是:模型给出的预测值有解释性,能回答"为什么是这个数字":
- “下个月 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/) 转载或引用必须申明原指尖魔法屋来源及源地址!
评论
使用 GitHub 账号登录后即可留言,支持 Markdown。