利用最小支持向量机LSSVM做时间序列预测模型,数据是单输入单输出的替换数据就可以用,代码内有注释,

最近帮楼下奶茶店老板救过个场——上周看他蹲在冰箱跟前扒坏的芋泥泥芋圆碎,半箱都要长毛了,说是上周刷了个本地芋泥节的预告瞎囤的。我心想这有啥,搞个单输入单输出的销量预测小脚本,替换下芋泥波波改成杨枝甘露,替换成本地每周六气温改成本地周末某商圈人流量,甚至随便改个股票收盘价、用电量序列,敲敲回车就能跑。选模型的时候专门挑了最省心、单输出友好的最小支持向量机(LSSVM),普通SVM搞回归有时候得找ε不敏感带的边边角角算半天二次规划,LSSVM直接改成了解线性方程组,快得像老板摇奶茶盖儿。

先给你们丢个完整带注释的Python脚本,不用特意搜lssvm库有没有更新或者会不会报错,我直接用numpy和scipy解了核心公式——自己捏轮子不是装逼,是方便你们调得更顺手,替换数据的时候绝对不会因为库版本不对卡壳。

import numpy as np
import pandas as pd
from scipy.linalg import solve
from sklearn.preprocessing import MinMaxScaler  # 这里用了个sklearn的归一化,新手不会踩单位大坑
import matplotlib.pyplot as plt  # 画个图直观看看老板芋泥节预告翻车那天的预测
plt.rcParams['font.sans-serif'] = ['SimHei']  # 解决中文乱码
plt.rcParams['axes.unicode_minus'] = False

# 替换数据!只需要改这里的csv路径和列名!比如csv里只有'杨枝甘露销量'就改成df['销量']
df = pd.read_csv('weekly_bubbletea.csv')  # 我的csv里是:日期,芋泥波波周销量
raw_data = df['芋泥波波周销量'].values.reshape(-1, 1)  # 必须转成二维,归一化和滑动窗口要用到

# 滑动窗口!超关键!比如用过去3周的销量预测第4周,这里window_size=3可以改
window_size = 3
X, y = [], []
for i in range(len(raw_data) - window_size):
    X.append(raw_data[i:i+window_size, 0])  # 输入:过去3周
    y.append(raw_data[i+window_size, 0])    # 输出:第4周
X = np.array(X)
y = np.array(y).reshape(-1, 1)  # 同样转二维

# 数据归一化!销量有时候差10倍,ML模型会疯
scaler_X = MinMaxScaler(feature_range=(0, 1))
scaler_y = MinMaxScaler(feature_range=(0, 1))
X_scaled = scaler_X.fit_transform(X)
y_scaled = scaler_y.fit_transform(y)

# 简单分训练集测试集,比如前80%训练,后20%看效果
train_ratio = 0.8
train_size = int(len(X_scaled) * train_ratio)
X_train, X_test = X_scaled[:train_size], X_scaled[train_size:]
y_train, y_test = y_scaled[:train_size], y_scaled[train_size:]

# ---------------------- 2. 手写LSSVM核心公式(RBF核,通用但效果还行) ----------------------
# RBF核的参数gamma,新手可以先默认1.0,后面调调老板芋泥销量应该更准
gamma = 1.0
# LSSVM的正则化参数C,控制拟合程度,别太小欠拟合,别太大过拟合,新手默认10.0
C = 10.0

def rbf_kernel(x1, x2, gamma):
    """计算RBF核矩阵,别纠结数学公式,记住它是把低维销量扔去高维找规律就行"""
    return np.exp(-gamma * np.sum((x1 - x2)**2, axis=1))  # 用广播机制加速,不用循环

# 构造线性方程组的系数矩阵Omega和右边的向量Y
n_train = len(X_train)
Omega = np.zeros((n_train, n_train))
for i in range(n_train):
    # 这里取了X_train[i]和整个X_train的差,省了一层循环
    Omega[i, :] = rbf_kernel(X_train[i].reshape(1, -1), X_train, gamma)
# 系数矩阵A = [0 1^T; 1 Omega + I/C]
A = np.block([
    [0, np.ones((1, n_train))],
    [np.ones((n_train, 1)), Omega + np.eye(n_train)/C]
])
# 右边的向量b = [0; y_train]
b = np.block([[0], y_train])

# 解线性方程组!scipy的solve比自己写高斯消元快多了
solution = solve(A, b)
bias = solution[0, 0]  # 提取偏置项b
alpha = solution[1:, 0]  # 提取拉格朗日乘子alpha

# ---------------------- 3. 预测与反归一化 ----------------------
def lssvm_predict(X_pred, X_train, alpha, bias, gamma):
    """预测函数,同样用广播机制"""
    n_pred = len(X_pred)
    y_pred_scaled = np.zeros(n_pred)
    for i in range(n_pred):
        kernel_pred = rbf_kernel(X_pred[i].reshape(1, -1), X_train, gamma)
        y_pred_scaled[i] = np.sum(alpha * kernel_pred) + bias
    return y_pred_scaled.reshape(-1, 1)

# 预测训练集和测试集,看看有没有过拟合老板芋泥节
y_train_pred_scaled = lssvm_predict(X_train, X_train, alpha, bias, gamma)
y_test_pred_scaled = lssvm_predict(X_test, X_train, alpha, bias, gamma)

# 反归一化,不然是0-1的数字,老板看不懂
y_train_pred = scaler_y.inverse_transform(y_train_pred_scaled)
y_test_pred = scaler_y.inverse_transform(y_test_pred_scaled)
y_train_true = scaler_y.inverse_transform(y_train)
y_test_true = scaler_y.inverse_transform(y_test)

# ---------------------- 4. 简单可视化和误差看看 ----------------------
# 可视化整个序列的真实值和预测值
plt.figure(figsize=(12, 6))
# 训练集和测试集的x轴拼接一下,看起来连贯
x_train = np.arange(window_size, window_size + train_size)
x_test = np.arange(window_size + train_size, window_size + train_size + len(y_test_true))
plt.plot(x_train, y_train_true, label='训练集真实销量', color='skyblue')
plt.plot(x_train, y_train_pred, label='训练集预测销量', color='dodgerblue', linestyle='--')
plt.plot(x_test, y_test_true, label='测试集真实销量', color='orange')
plt.plot(x_test, y_test_pred, label='测试集预测销量', color='orangered', linestyle='--')
# 标记老板芋泥节那周!假设测试集第5周是(随便举的,你们不用)
if len(x_test) > 5:
    plt.scatter(x_test[4], y_test_true[4], color='red', marker='*', s=200, label='芋泥节瞎囤周')
plt.xlabel('时间周数')
plt.ylabel('芋泥波波周销量')
plt.title('楼下芋泥波波周销量LSSVM预测')
plt.legend()
plt.show()

# 算个MAE(平均绝对误差),老板也看得懂:平均每周差多少杯
mae_train = np.mean(np.abs(y_train_true - y_train_pred))
mae_test = np.mean(np.abs(y_test_true - y_test_pred))
print(f"训练集平均每周差:{mae_train:.0f}杯")
print(f"测试集平均每周差:{mae_test:.0f}杯")

接下来捏捏核心公式这块,别被block吓到,其实就是把普通SVM的二次规划目标函数和约束条件改了:普通SVM是找个窄窄的ε不敏感带,让尽量多的点落在带子里,超参数要调C(带外点的惩罚)和ε(带的宽度),有时候ε调不对直接白搭;LSSVM直接把约束条件里的不等式(ξi≥0, ξi^≥0)改成了等式,损失函数换成了ξi²+ξi^2,就变成解Ax=b的线性方程了——新手不用懂具体推,只需要记住:这里面只有RBF核的gamma和正则化C两个超参数,新手先随便试gamma=0.5/1.0/2.0,C=1/10/100,基本能凑合用,要精准的话可以加个网格搜索(比如把gamma和C放数组里循环跑算MAE最小的),但楼下老板要的是快和能用,懒得搞太复杂的。

滑动窗口那块也很重要!这个不是LSSVM独有的,所有单输入单输出时间序列预测都得搞这个——时间序列的数据不是独立的,这周卖多少肯定和上周上上周有关,windowsize就是你选“多少周相关”,老板奶茶店可能3周-4周就行,股票收盘价可能得1周-2周?随便改,脚本里直接改windowsize数字。

利用最小支持向量机LSSVM做时间序列预测模型,数据是单输入单输出的替换数据就可以用,代码内有注释,

归一化也是必加的新手友好操作!比如老板芋泥波波销量最高1000杯最低100杯,杨枝甘露销量最高2000杯最低200杯,如果不归一化直接扔进去,杨枝甘露的2000杯会把LSSVM的注意力全吸走,预测芋泥波波就不准了,用MinMaxScaler把所有数据缩到0-1之间,公平竞争。

可视化那块老板蹲在我手机屏幕上看了好久,指着红色星星说:“你看你看!上周芋泥节真实销量只有800,预测有1200!我要是早看这个只囤一半芋泥泥就好了!”我拍了拍他肩膀:“下次杨枝甘露节记得把列名改了再来找我。”

对了,新手替换数据的时候真的只需要改第一步的csv路径和列名!比如你们用的是csv里的'苹果股价收盘价',就把rawdata = df['芋泥波波周销量']改成rawdata = df['苹果股价收盘价'],滑动窗口window_size改成5(比如过去5天预测第6天),其他代码一个字不用动,敲敲回车就能跑。

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐