预测算法 | Holt Winter季节性指数平滑法 附JAVA代码

简介: 预测算法 | Holt Winter季节性指数平滑法 附JAVA代码

最近课上老师让我们做一个销量预测的作业,刚好小编被分配到了这个算法,所以在这里写一下,mark一下。


Holt Winter季节性指数平滑本质上就是三次指数平滑法,添加了一个新的参数c来表示平滑后的趋势。


Holt Winter季节性指数平滑有累加和累乘两种方法。


下面是累加的三次指数平滑:


微信图片_20220421155228.png


累加模型适用于具有线性趋势且季节效应不随时间变化的序列。


累乘式季节性对应的等式为:


微信图片_20220421155231.png


累乘模型适用于具有线性趋势且季节效应随序列量级发生变化的序列。


其中c_t为周期性的分量,代表周期的长度,F_{t+m}为模型预测的等式。s,b,t代表着水平、趋势和季节。


初始值的选取对于算法整体的影响不是特别大,通常的取值为s_0=x_0,b_0=x_1-x_0,累加时c_0=0,累乘时c_0=1。


α,ß,γ的值都位于[0,1]之间。


代码:代码下载移步留言区。

import java.util.ArrayList;
import java.util.List;
/**
 * 三次指数平滑预测,最多外推一个周期
 * @author Administrator
 *
 */
public class HoltWintersTripleExponentialImpl {
    static double[] alphas=new double[]{0.1,0.2,0.3,0.4,0.5,0.6,0.7,0.8,0.9};
    public static Double getbetterforecast(List<Integer> y, int period, int m) {
        double[] result=betterforecast(y,period,m);
        return result[result.length-1];
    }
    /**
     * 求出最佳预测
     * @param y
     * @param alpha
     * @param beta
     * @param gamma
     * @param period
     * @param m
     * @param debug
     * @return
     */
    public static double[] betterforecast(List<Integer> y, int period, int m) {
        double[] result = null;
        double avg=0;
        for(int i=0;i<alphas.length;i++){
            int sum=0;
            double[] temp=forecast(y,alphas[i],0.25,0,period,m,false);
            for(int j=0;j<y.size();j++){
                sum+=Math.abs(y.get(j)-temp[j]);
            }
            if(i==0){
                avg=sum/y.size();
                result=temp;
            }
            if(avg<(sum/y.size())){
                result=temp;
            }
        }
        return result;
    }
    /**
     * This method is the entry point. It calculates the initial values and returns the forecast
     * for the m periods.
     * @param y 数据集合
     * @param alpha - Exponential smoothing coefficients for level, trend, seasonal components.
     * @param beta - Exponential smoothing coefficients for level, trend, seasonal components.
     * @param gamma - Exponential smoothing coefficients for level, trend, seasonal components.
     * @param perdiod - 一个完整周期的长度,至少需要两个周期 
     * @param m -预测的个数
     * @param debug - 是否打印详细信息
     */
    public static double[] forecast(List<Integer> y, double alpha, double beta,
            double gamma, int period, int m, boolean debug) {
        if (y == null) {
            return null;
        }
        //获取季节数
        int seasons = y.size() / period;
        //平滑值st0
        double a0 = calculateInitialLevel(y, period);
        //bt0
        double b0 = calculateInitialTrend(y, period);
        //初始化季节因子
        double[] initialSeasonalIndices = calculateSeasonalIndices(y, period, seasons);
        if (debug) {
            System.out.println(String.format(
                    "Total observations: %d, Seasons %d, Periods %d", y.size(),
                    seasons, period));
            System.out.println("Initial level value a0: " + a0);
            System.out.println("Initial trend value b0: " + b0);
            printArray("Seasonal Indices: ", initialSeasonalIndices);
        }
        double[] forecast = calculateHoltWinters(y, a0, b0, alpha, beta, gamma,
                initialSeasonalIndices, period, m, debug);
        if (debug) {
            printArray("Forecast", forecast);
        }
        return forecast;
    }
    /**
     * This method realizes the Holt-Winters equations.
     * 计算进行预测
     * @param y
     * @param a0
     * @param b0
     * @param alpha
     * @param beta
     * @param gamma
     * @param initialSeasonalIndices
     * @param period
     * @param m
     * @param debug
     * @return - Forecast for m periods.  
     */
    private static double[] calculateHoltWinters(List<Integer> y, double a0, double b0, double alpha,
            double beta, double gamma, double[] initialSeasonalIndices, int period, int m, boolean debug) {
        //整体平滑 st=alpha * y [i] / It [i-period] +(1.0  -  alpha)*(St [i  -  1] + Bt [i  -  1])
        double[] St = new double[y.size()];
        //趋势平滑 Bt [i] = gamma *(St [i] -St [i-1])+(1-gamma)* Bt [i-1]
        double[] Bt = new double[y.size()];
        //季节性平滑It [i] = beta * y [i] / St [i] +(1.0  -  beta)* It [i  -  period]
        double[] It = new double[y.size()];
        //预测Ft [i + m] =(St [i] +(m * Bt [i]))* It [i  -  period + m]
        double[] Ft = new double[y.size() + m];
        //Initialize base values  初始化
        St[1] = a0;
        Bt[1] = b0;
        //季节性平滑
        for (int i = 0; i < period; i++) {
            It[i] = initialSeasonalIndices[i];
        }
        //预测初始化
        Ft[0+m] = (St[0] + (m * Bt[0])) * It[0];//因为Bt [0] = 0,所以这实际上是0
        Ft[m + 1] = (St[1] + (m * Bt[1])) * It[1];//Forecast starts from period + 2
        //Start calculations
        for (int i = 2; i < y.size(); i++) {
            //Calculate overall smoothing  计算所有的平滑值
            if((i - period) >= 0) {
                St[i] = alpha * y.get(i) / It[i - period] + (1.0 - alpha) * (St[i - 1] + Bt[i - 1]);
            } else {
                St[i] = alpha * y.get(i) + (1.0 - alpha) * (St[i - 1] + Bt[i - 1]);
            }
            //Calculate trend smoothing  计算所有的趋势
            Bt[i] = gamma * (St[i] - St[i - 1]) + (1 - gamma) * Bt[i - 1];
            //Calculate seasonal smoothing  计算所有的季节趋势
            if((i - period) >= 0) {
                It[i] = beta * y.get(i) / St[i] + (1.0 - beta) * It[i - period];
            }
            //Calculate forecast  计算预期值
            if( ((i + m) >= period) ){
                Ft[i + m] = (St[i] + (m * Bt[i])) * It[i - period + m];
                if(Ft[i+m]<=0){
                    Ft[i+m]=1;
                }
            }
            if(debug){
                System.out.println(String.format(
                        "i = %d, y = %d, S = %f, Bt = %f, It = %f, F = %f", i,
                        y.get(i), St[i], Bt[i], It[i], Ft[i]));
            }
        }
        return Ft;
    }
    /**
     * See: http://robjhyndman.com/researchtips/hw-initialization/
     * 1st period's average can be taken. But y[0] works better.
     * 
     * @return - Initial Level value i.e. St[1]
     */
    private static double calculateInitialLevel(List<Integer> y, int period) {
        /**     
         double sum = 0;
        for (int i = 0; i < period; i++) {
            sum += y[i];
        }
        return sum / period;
         **/
        return y.get(0);
    }
    /**
     * See: http://www.itl.nist.gov/div898/handbook/pmc/section4/pmc435.htm
     * 
     * @return - Initial trend - Bt[1]
     */
    private static double calculateInitialTrend(List<Integer> y, int period){
        double sum = 0;
        for (int i = 0; i < period; i++) {          
            sum += (y.get(period+i)- y.get(i));
        }
        return sum / (period * period);
    }
    /**
     * See: http://www.itl.nist.gov/div898/handbook/pmc/section4/pmc435.htm
     * 计算及季节性指数
     * @return - Seasonal Indices.
     */
    private static double[] calculateSeasonalIndices(List<Integer> y, int period, int seasons){
        double[] seasonalAverage = new double[seasons];
        double[] seasonalIndices = new double[period];
        double[] averagedObservations = new double[y.size()];
        //获取所有季节各个周期的平均值
        for (int i = 0; i < seasons; i++) {
            for (int j = 0; j < period; j++) {
                seasonalAverage[i] += y.get((i * period) + j);
            }
            seasonalAverage[i] /= period;
        }
        //平均观测值
        for (int i = 0; i < seasons; i++) {
            for (int j = 0; j < period; j++) {
                //真正的值/平均值
                averagedObservations[(i * period) + j] =y.get((i * period) + j) / seasonalAverage[i];               
            }           
        }
        //初始季节性指数
        for (int i = 0; i < period; i++) {
            for (int j = 0; j < seasons; j++) {
                seasonalIndices[i] += averagedObservations[(j * period) + i];
            }           
            seasonalIndices[i] /= seasons;
        }
        return seasonalIndices;
    }
    /**
     * Utility method to pring array values.
     * 
     * @param description
     * @param data
     */
    private static void printArray(String description, double[] data){
        System.out.println(String.format("******************* %s *********************", description));
        for (int i = 0; i < data.length; i++) {
            System.out.println(data[i]);
        }
        System.out.println(String.format("*****************************************************************", description));
    }
    /**
     * 求出二次最佳预测
     * @param y
     * @param m
     * @return
     */
    public static double[] betterSec(List<Integer> y, int m) {
        double[] result = null;
        double avg=0;
        for(int i=0;i<alphas.length;i++){
            int sum=0;
            double[] temp=getSecExpect(y,m,alphas[i]);
            for(int j=0;j<y.size();j++){
                sum+=Math.abs(y.get(j)-temp[j]);
            }
            if(i==0){
                avg=sum/y.size();
                result=temp;
            }
            if(avg<sum/y.size()){
                result=temp;
            }
        }
        return result;
    }
    /**
     * 二次指数平滑法求预测值 预测线性趋势
     * 
     * @param list
     *            基础数据集合
     * @param year
     *            未来第几期
     * @param modulus
     *            平滑系数
     * @return 预测值
     */
    public static double[] getSecExpect(List<Integer> list, int year, Double modulus) {
        if (list.size() < 20 || modulus <= 0 || modulus >= 1) {
            return null;
        }
        double[] ft=new double[list.size()+year];
        Double modulusLeft = 1 - modulus;
        // 一次预测 >20项默认第一个值为初始值
        Double lastIndex = Double.parseDouble(list.get(0).toString());
        // 二次预测 >20项默认第一个值为初始值
        Double lastSecIndex = Double.parseDouble(list.get(0).toString());
        for (int i=0;i<list.size();i++) {
            // st1
            lastIndex = modulus * list.get(i) + modulusLeft * lastIndex;
            // st2
            lastSecIndex = modulus * lastIndex + modulusLeft * lastSecIndex;
            // AT
            Double a = 2 * lastIndex - lastSecIndex;
            // BT
            Double b = (modulus / modulusLeft) * (lastIndex - lastSecIndex);
            ft[i+1]=a+b*year;
            if(ft[i+1]<=0){
                ft[i+1]=1;
            }
        }
        return ft;
    }
    /**
     * 三次指数平滑法求预测值 预测线性趋势
     * 
     * @param list
     *            基础数据集合
     * @param year
     *            未来第几期
     * @param modulus
     *            平滑系数
     * @return 预测值
     */
    public static Double getThridExpect(List<Integer> list, int year, Double modulus) {
        if (list.size() < 20 || modulus <= 0 || modulus >= 1) {
            return null;
        }
        Double modulusLeft = 1 - modulus;
        // 一次预测 >20项默认第一个值为初始值
        Double lastIndex =(double) ((list.get(0)+list.get(1)+list.get(2))/3);
        // 二次预测 >20项默认第一个值为初始值
        Double lastSecIndex = lastIndex;
        // 三次预测 >20项默认第一个为初始值
        Double lastThreadIndes = lastIndex;
        for (Integer data : list) {
            // st1
            lastIndex = modulus * data + modulusLeft * lastIndex;
            // st2
            lastSecIndex = modulus * lastIndex + modulusLeft * lastSecIndex;
            // st3
            lastThreadIndes = modulus * lastSecIndex + modulusLeft * lastThreadIndes;
        }
        // AT
        Double a = 3 * lastIndex - 3 * lastSecIndex + lastThreadIndes;
        // BT
        Double b = (modulus / (2 * modulusLeft * modulusLeft)) * ((6 - 5 * modulus) * lastIndex
                - 2 * (5 - 4 * modulus) * lastSecIndex + (4 - 3 * modulus) * lastThreadIndes);
        // CT
        Double c = ((modulus * modulus) / (2 * modulusLeft *modulusLeft))
                * (lastIndex - 2 * lastSecIndex + lastThreadIndes);
        Double except=a+b*year+c*year*year;
        return except<0?0:except;
    }
}

代码来源CSDN:

作者:WX5991

原文:https://blog.csdn.net/WX5991/article/details/80082041 


附上程序效果:


微信图片_20220421155234.jpg


这是一组预测得比较好的数据。。。


相关文章
|
5天前
|
Java 程序员 图形学
程序员教你用代码制作飞翔的小鸟--Java小游戏,正好拿去和给女神一起玩
《飞扬的小鸟》Java实现摘要:使用IntelliJ IDEA和JDK 16开发,包含小鸟类`Bird`,处理小鸟的位置、速度和碰撞检测。代码示例展示小鸟图像的加载、绘制与旋转。同时有`Music`类用于循环播放背景音乐。游戏运行时检查小鸟是否撞到地面、柱子或星星,并实现翅膀煽动效果。简单易懂,可直接复制使用。
|
21小时前
|
机器学习/深度学习 算法 API
【Paddle】PCA线性代数基础 + 领域应用:人脸识别算法(1.1w字超详细:附公式、代码)
【Paddle】PCA线性代数基础 + 领域应用:人脸识别算法(1.1w字超详细:附公式、代码)
6 0
|
1天前
|
Java
Java的取余如何编写代码
【5月更文挑战第9天】Java的取余如何编写代码
13 5
|
2天前
|
数据采集 算法 数据可视化
python实现时序平滑算法SG滤波器
python实现时序平滑算法SG滤波器
|
4天前
|
Java
代码实例演示Java字符串与输入流互转
代码实例演示Java字符串与输入流互转
|
4天前
|
存储 安全 Java
掌握8条泛型规则,打造优雅通用的Java代码
掌握8条泛型规则,打造优雅通用的Java代码
掌握8条泛型规则,打造优雅通用的Java代码
|
4天前
|
算法 关系型数据库 C语言
卡尔曼滤波简介+ 算法实现代码(转)
卡尔曼滤波简介+ 算法实现代码(转)
16 4
|
5天前
|
数据库连接
java+ssm+vue代码视频学习讲解
java+ssm+vue代码视频学习讲解
7 0
|
5天前
|
SQL 缓存 算法
优化你的Java代码:性能调优技巧
优化你的Java代码:性能调优技巧
12 0
|
5天前
|
运维 算法
基于改进遗传算法的配电网故障定位(matlab代码)
基于改进遗传算法的配电网故障定位(matlab代码)