进销存系统智能销售预测与需求预测算法
销售预测概述
准确的销售预测是进销存系统优化库存周转、降低运营成本的关键。通过对历史销售数据的分析和机器学习算法的应用,可以实现更准确的需求预测,为采购决策提供数据支撑。本文介绍进销存系统中智能销售预测的技术实现方案。
系统架构设计
销售预测系统采用数据驱动的架构设计:
| 层次 | 功能描述 | 技术选型 |
|---|---|---|
| 数据层 | 销售数据、库存数据、外部数据采集 | MySQL、Python |
| 特征层 | 特征工程、特征选择、数据预处理 | Pandas、NumPy |
| 模型层 | 时序预测、机器学习模型训练与推理 | Prophet、XGBoost |
| 应用层 | 预测展示、采购建议、预警提醒 | Koa.js、Web |
核心算法实现
1. 时间序列特征提取
从销售数据中提取有效的特征:
// 特征工程服务
class FeatureEngineering {
constructor() {
this.holidays = this.getHolidayList();
}
// 提取时间序列特征
extractTimeFeatures(date) {
const d = new Date(date);
return {
year: d.getFullYear(),
month: d.getMonth() + 1,
day: d.getDate(),
dayOfWeek: d.getDay(),
dayOfYear: this.getDayOfYear(d),
weekOfYear: this.getWeekOfYear(d),
quarter: Math.floor((d.getMonth() + 3) / 3),
isWeekend: d.getDay() === 0 || d.getDay() === 6,
isHoliday: this.isHoliday(d),
isMonthStart: d.getDate() === 1,
isMonthEnd: d.getDate() === this.getDaysInMonth(d)
};
}
// 滞后特征
createLagFeatures(data, lags = [1, 7, 14, 30]) {
const result = data.map((item, index) => {
const newItem = { ...item };
for (const lag of lags) {
if (index >= lag) {
newItem[`lag_${lag}`] = data[index - lag].sales;
}
}
return newItem;
});
return result;
}
// 滚动统计特征
createRollingFeatures(data, windows = [7, 14, 30]) {
return data.map((item, index) => {
const newItem = { ...item };
for (const window of windows) {
if (index >= window) {
const windowData = data.slice(index - window, index);
const sales = windowData.map(d => d.sales);
newItem[`rolling_mean_${window}`] = this.mean(sales);
newItem[`rolling_std_${window}`] = this.std(sales);
newItem[`rolling_max_${window}`] = Math.max(...sales);
newItem[`rolling_min_${window}`] = Math.min(...sales);
}
}
return newItem;
});
}
// 同比环比特征
createYoYFeatures(data, productId) {
const productData = data.filter(d => d.productId === productId);
return productData.map((item, index) => {
const newItem = { ...item };
// 去年同月
const lastYear = productData.find(d =>
d.date.getFullYear() === item.date.getFullYear() - 1 &&
d.date.getMonth() === item.date.getMonth()
);
if (lastYear) {
newItem.yoy_sales = lastYear.sales;
newItem.yoy_growth = (item.sales - lastYear.sales) / lastYear.sales;
}
// 上月
const lastMonth = productData.find(d =>
d.date.getFullYear() === item.date.getFullYear() &&
d.date.getMonth() === item.date.getMonth() - 1
);
if (lastMonth) {
newItem.mom_sales = lastMonth.sales;
newItem.mom_growth = (item.sales - lastMonth.sales) / lastMonth.sales;
}
return newItem;
});
}
// 获取节假日的日期列表
getHolidayList() {
// 这里可以从配置文件或API获取
return [
'2025-01-01', // 元旦
'2025-02-10', // 春节
'2025-04-04', // 清明节
'2025-05-01', // 劳动节
'2025-06-10', // 端午节
];
}
isHoliday(date) {
const dateStr = date.toISOString().split('T')[0];
return this.holidays.includes(dateStr);
}
mean(arr) {
return arr.reduce((a, b) => a + b, 0) / arr.length;
}
std(arr) {
const m = this.mean(arr);
return Math.sqrt(arr.reduce((sq, n) => sq + Math.pow(n - m, 2), 0) / arr.length);
}
getDayOfYear(date) {
const start = new Date(date.getFullYear(), 0, 0);
const diff = date - start;
return Math.floor(diff / (1000 * 60 * 60 * 24));
}
getWeekOfYear(date) {
const start = new Date(date.getFullYear(), 0, 1);
const diff = date - start;
return Math.ceil((diff + start.getDay() + 1) / (7 * 24 * 3600 * 1000));
}
getDaysInMonth(date) {
return new Date(date.getFullYear(), date.getMonth() + 1, 0).getDate();
}
}
2. Prophet 模型预测
使用 Facebook Prophet 进行销售预测:
// Prophet 预测服务
class SalesForecastService {
constructor() {
this.model = null;
this.productModels = new Map();
}
// 准备训练数据
prepareTrainingData(salesData) {
return salesData.map(item => ({
ds: new Date(item.date),
y: item.sales,
product_id: item.productId,
category: item.category,
price: item.price,
promotion: item.promotion || 0
}));
}
// 训练单个产品模型
async trainProductModel(productId, salesData) {
const data = this.prepareTrainingData(salesData);
// 配置 Prophet 参数
const config = {
growth: 'linear',
yearly_seasonality: true,
weekly_seasonality: true,
daily_seasonality: false,
holidays: this.getHolidayFeatures(),
changepoint_prior_scale: 0.05,
seasonality_prior_scale: 10,
seasonality_mode: 'multiplicative'
};
// 训练模型
const model = new Prophet(config);
model.add regressor('promotion');
// 添加外部 regressor
model.fit(data.map(d => ({
ds: d.ds,
y: d.y,
promotion: d.promotion
})));
this.productModels.set(productId, model);
return model;
}
// 预测未来销售
async forecast(productId, days = 30) {
const model = this.productModels.get(productId);
if (!model) {
throw new Error(`Model for product ${productId} not found`);
}
// 生成预测日期
const future = [];
const today = new Date();
for (let i = 0; i < days; i++) {
const date = new Date(today);
date.setDate(date.getDate() + i);
future.push({ ds: date, promotion: 0 });
}
// 进行预测
const forecast = model.predict(future);
return forecast.map(item => ({
date: item.ds,
predicted: item.yhat,
lower: item.yhat_lower,
upper: item.yhat_upper,
trend: item.trend,
weekly: item.weekly || 0,
yearly: item.yearly || 0
}));
}
// 批量预测
async batchForecast(productIds, days = 30) {
const results = {};
for (const productId of productIds) {
try {
results[productId] = await this.forecast(productId, days);
} catch (error) {
console.error(`Failed to forecast product ${productId}:`, error);
results[productId] = null;
}
}
return results;
}
// 模型评估
evaluateModel(productId, testData) {
const model = this.productModels.get(productId);
if (!model) return null;
const predictions = model.predict(testData.map(d => ({ ds: d.date })));
const actual = testData.map(d => d.sales);
const predicted = predictions.map(p => p.yhat);
return {
mae: this.calculateMAE(actual, predicted),
mape: this.calculateMAPE(actual, predicted),
rmse: this.calculateRMSE(actual, predicted)
};
}
calculateMAE(actual, predicted) {
const n = actual.length;
const sum = actual.reduce((acc, val, i) => acc + Math.abs(val - predicted[i]), 0);
return sum / n;
}
calculateMAPE(actual, predicted) {
const n = actual.length;
const sum = actual.reduce((acc, val, i) => acc + Math.abs((val - predicted[i]) / val), 0);
return sum / n * 100;
}
calculateRMSE(actual, predicted) {
const n = actual.length;
const sum = actual.reduce((acc, val, i) => acc + Math.pow(val - predicted[i], 2), 0);
return Math.sqrt(sum / n);
}
}
3. XGBoost 机器学习预测
使用 XGBoost 进行多因素销售预测:
// XGBoost 预测模型
class XGBoostForecastModel {
constructor() {
this.model = null;
this.featureColumns = [];
}
// 准备训练数据
prepareData(salesData) {
const featureEngineer = new FeatureEngineering();
// 提取特征
const features = salesData.map(item => {
const timeFeatures = featureEngineer.extractTimeFeatures(item.date);
return {
...timeFeatures,
price: item.price,
promotion: item.promotion || 0,
inventory: item.inventory || 0,
competitor_price: item.competitorPrice || 0,
temperature: item.temperature || 20,
category_encoded: this.encodeCategory(item.category)
};
});
this.featureColumns = Object.keys(features[0]);
const X = features.map(f => Object.values(f));
const y = salesData.map(d => d.sales);
return { X, y };
}
// 训练模型
async train(salesData) {
const { X, y } = this.prepareData(salesData);
this.model = xgboost.train({
objective: 'reg:squarederror',
max_depth: 6,
learning_rate: 0.1,
n_estimators: 100,
min_child_weight: 1,
subsample: 0.8,
colsample_bytree: 0.8
}, X, y);
return this.model;
}
// 预测
predict(inputData) {
if (!this.model) {
throw new Error('Model not trained');
}
const featureEngineer = new FeatureEngineering();
const features = {
...featureEngineer.extractTimeFeatures(inputData.date),
price: inputData.price,
promotion: inputData.promotion || 0,
inventory: inputData.inventory || 0,
competitor_price: inputData.competitorPrice || 0,
temperature: inputData.temperature || 20,
category_encoded: this.encodeCategory(inputData.category)
};
const X = [this.featureColumns.map(col => features[col])];
return this.model.predict(X)[0];
}
// 特征重要性
getFeatureImportance() {
const importance = this.model.getScore(importanceType: 'gain');
return Object.entries(importance)
.map(([feature, score]) => ({ feature, score }))
.sort((a, b) => b.score - a.score);
}
// 模型序列化
saveModel(path) {
this.model.saveModel(path);
}
// 模型加载
loadModel(path) {
this.model = xgboost.Booster({ nthread: 4 });
this.model.loadModel(path);
}
encodeCategory(category) {
const categoryMap = {
'电子产品': 1,
'服装': 2,
'食品': 3,
'家居': 4,
'图书': 5
};
return categoryMap[category] || 0;
}
}
预测结果应用
将预测结果应用到采购决策:
// 采购建议生成
class PurchaseRecommendation {
constructor(forecastService, inventoryService) {
this.forecastService = forecastService;
this.inventoryService = inventoryService;
}
// 生成采购建议
async generateRecommendations(productIds, forecastDays = 30) {
const recommendations = [];
for (const productId of productIds) {
// 获取销售预测
const forecast = await this.forecastService.forecast(productId, forecastDays);
const totalPredicted = forecast.reduce((sum, f) => sum + f.predicted, 0);
// 获取当前库存
const currentInventory = await this.inventoryService.getInventory(productId);
// 获取安全库存
const safetyStock = await this.calculateSafetyStock(productId);
// 计算建议采购量
const recommendedQty = Math.max(0,
Math.ceil(totalPredicted) - currentInventory + safetyStock
);
// 计算建议采购日期
const runOutDate = await this.estimateRunOutDate(productId, currentInventory);
const purchaseDate = new Date(runOutDate);
purchaseDate.setDate(purchaseDate.getDate() - 7); // 提前7天采购
recommendations.push({
productId,
predictedSales: Math.ceil(totalPredicted),
currentInventory,
safetyStock,
recommendedQty,
runOutDate,
recommendedPurchaseDate: purchaseDate,
urgency: this.calculateUrgency(currentInventory, safetyStock)
});
}
return recommendations.sort((a, b) => b.urgency - a.urgency);
}
// 计算安全库存
async calculateSafetyStock(productId) {
const sales = await this.getHistoricalSales(productId, 90);
const avgDaily = sales.reduce((a, b) => a + b, 0) / 90;
const stdDev = this.calculateStdDev(sales);
// 安全库存 = Z值 * 标准差 * sqrt(补货周期)
const zValue = 1.65; // 95%服务水平
const leadTime = 7; // 7天补货周期
return Math.ceil(zValue * stdDev * Math.sqrt(leadTime));
}
calculateStdDev(arr) {
const mean = arr.reduce((a, b) => a + b, 0) / arr.length;
const squareDiffs = arr.map(val => Math.pow(val - mean, 2));
return Math.sqrt(squareDiffs.reduce((a, b) => a + b, 0) / arr.length);
}
}
模型选择策略
不同场景下的模型选择建议:
| 场景 | 推荐模型 | 原因 |
|---|---|---|
| 季节性商品 | Prophet | 内置季节性分解 |
| 多因素影响 | XGBoost | 支持多特征输入 |
| 新品预测 | 相似品参考 | 无历史数据 |
| 促销活动 | ARIMA + 事件 | 处理异常波动 |
总结
智能销售预测是进销存系统智能化的重要组成部分,通过合理选择预测算法和特征工程,可以显著提升预测准确率。在实际应用中,需要根据业务特点选择合适的模型,并持续优化和迭代,以适应市场变化。