為什麼需要經濟理論來預測經濟趨勢:比較機器學習與計量經濟

近幾年隨著機器學習的崛起,許多人開始使用機器學習的方法來做為預測分析,而經濟學其實在這方面算是先驅的科目之一,例如現任台大校長管中閔與其導師早在1995年的”Forecasting exchange rates using feedforward and recurrent networks“這篇裡面神經網路的方法來預測匯率,這並不新。而許多量化基金其實也採用類似的作法,而且是越來越進步。

這讓我們不禁要問:”那我還學經濟學幹嘛”,尤其是對於興趣其實是預測經濟成長甚至是股價變化的人來講,似乎懂怎麼處理資料以及演算法甚至比起懂經濟學理論還要重要?

在我自己常常學習Python於經濟學運用的社群QuantEcon上面有一篇文章Forecasting Inflation Using VAR: A Horserace Between Traditional and ML Approaches就透過總體經濟常用的模型告訴我們;經濟學理論還是很重要的,而我這一篇主要就根據他的做法重新調整(因為他有些code其實跑出來會出現bug),說明為什麼機器學習只是輔助,而我們需要更多理解才能做出更好的經濟預測。

總體經濟學家怎麼預測經濟?

如果閱讀經新聞,其實應該對於聯準會或者美國經濟研究局做出經濟預測之類的並不陌生,而經濟學家預測的工具其實很多,其中對於我們這種以總體經濟為主的人而言,一種名為向量自回歸模型(簡稱為VAR)的工具是最常使用的,當初由Christopher Sims於1980的”Macroeconomics and Reality“提出,這也讓他在2011拿下了諾貝爾經濟學獎。這是簡單的介紹,而對於經濟學沒啥興趣的可以直接跳過這一段,下一段直接跟你說要怎麼用Python寫VAR。

這個中文聽起來很可怕的模型到底是什麼?真的那麼困難嗎?其實並沒有,他只是名稱聽起來可怕,但是概念是很簡單的,但要理解這個概念讓我們先回到自回歸模型(簡稱AR),所謂的自回歸模型如同下面顯示的一樣,也就是我拿過去的資料來預估現在(甚至未來)的走勢,例如明天的股票價格=a*昨天股價+b*前天股價+…..+誤差,這在經濟學上稱為自回歸模型,因為他是自己跟自己回歸:

但這合理嗎?有時候影響未來的可能不只過去的價格,還有其他事物過去的價格,例如現在的股價可能取決於過去的股價、過去的股利、過去的營收等等,而這些事物彼此也會互相干擾,於是我們需要一個矩陣來把所有東西放在一起,而這就被稱為向量自回歸模型,而數學上會變得跟下面這條公式一樣,看起來很可怕但是其實跟上面那一條一模一樣,只是多了別的變數例如y1,y2,y3….yn,真的沒有什麼特別的:

這個基本的模型後來有許多變化,其中最有名是用來預測油價的Structural VAR以及Sims本人大力推廣的Bayesian VAR,而所有真的在做總體經濟的人絕對都或多或少會拿這個來做為基本線,總體經濟學家們可能會把石油、通膨、GDP、失業率等等放進去裡面作為預測,直到今天一樣是很好用的工具。

如果你就讀一個比較嚴謹的經濟學碩士甚至博士,而你有修總體經濟量化相關的課程,試著證明這裏面的一些特質,以及如何判斷過去的資料可以抓到哪一天才能提供精準的預測,這些等等就會是你應該學習以及證明順便哀號的,但這篇文章沒打算把地獄重現人世,那些東西留給可憐的經濟系學生就好。

如果對於VAR有想更加理解的人,可以去閱讀維基百科的英文頁面連結,中文的很爛不要看也罷。而我們就拿這個模型來比較一下經濟學的理論以及機器學習模型的預測能力,順便講一下什麼叫做overfitting這個問題,以及為何你不應該只仰賴機器學習作為預測的工具。

用Python視覺化經濟學理論

今天我們來預測最常見的一個變數:通貨膨脹,而問題來了,要用什麼來預測呢?

總體經濟學有兩大理論:菲利浦曲線以及歐肯定律,前者跟我們說通膨以及失業率成反比,也就是通膨越高的話失業率越低。後者則是跟我們說失業率以及經濟成長成反比,也就是失業率越高的話經濟成長越低,而我想經濟成長與股價又有所關係。同時,利率絕對也跟通朋友關係,於是我們可以先來測試一下失業率、股價、個人所得以及通膨之間的關係,首先用下面的code來設定一下環境:

import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline 
import pandas as pd 
import seaborn as sns
sns.set()
import datetime as dt
import warnings
warnings.simplefilter('ignore')

from sklearn import (linear_model, metrics, neural_network, pipeline, preprocessing, model_selection)
import statsmodels.formula.api as sm

!pip install xgboost
from xgboost import XGBRegressor, plot_importance

這些都只是常用的工具,沒什特別的,方便我們視覺處理資料而已。

而我們用的資料會是聯準會的聖路易斯安那分行(簡稱FRED)的資料,而全部的csv檔可以在Michael McCrackenu以及Serena Ng的網站上找到,所以以下的code只是單純處理資料而已:

fred=pd.read_csv('https://s3.amazonaws.com/files.fred.stlouisfed.org/fred-md/monthly/current.csv')
fred = fred.drop(0)
fred["sasdate"] = pd.to_datetime(fred["sasdate"])
fred = fred.rename(columns={"sasdate": "date"})
fred = fred.set_index("date")

# Stationarity in yearly percentage change
for c in list(fred.columns.values):
    fred[c + "_diff"] = (fred[c] - fred[c].shift(12))/fred[c].shift(12) * 100

最後那一行只是把資料年化稍微做處理而已,上面那幾行則是把資料的日期統整一下,沒什麼特別的,而我們接下來的code可以來視覺化資料,上面那段只是寫個loop,下面則是把圖給教出來而已,而結果會跟圖二一樣:

def scatter_plot(data, col_1, col_2, color):
    
   
    
    fig, ax = plt.subplots(figsize=(10,4))
    
    ax.scatter(x=data[col_1], y=data[col_2], alpha=0.4, color=color)
    ax.set_xlabel("Inflation", fontname="Verdana")
    
    if col_2 == "UNRATE":
        ax.set_title("Inflation and unemployment rate", fontsize=16, fontname="Verdana", loc="left")
        ax.set_ylabel("Unemployment rate", fontname="Verdana")
    elif col_2 == "S&P 500":
        ax.set_title("Inflation and S&P500", fontsize=16, fontname="Verdana", loc="left")
        ax.set_ylabel("S&P500", fontname="Verdana")
    elif col_2 =="FEDFUNDS":
        ax.set_title("Inflation and Federal Fund Rate",fontsize=16, fontname="Verdana",loc="left")
        ax.set_ylabel("Federal Fund Rate",fontname="Verdana")
    else:
        ax.set_title("Inflation and Real Personal Income", fontsize=16, fontname="Verdana", loc="left")
        ax.set_ylabel("RPI", fontname="Verdana")
for f,c in dict(zip(["UNRATE", "S&P 500","FEDFUNDS","RPI"], ["navy", "firebrick","green", "orange"])).items():
    scatter_plot(data=fred, col_1="CPIAUCSL_diff", col_2=f, color=c)
圖二(由上到下):失業率、股價、聯準會利率、實質個人所得與通膨的關係

從圖二可以看到,第三張圖的利率以及通膨的關係最為明顯,這非常符合經濟學的直覺,而個人所得以及失業率也有明顯的關係,但是股價則沒啥關聯,而在這樣的情況下,拿股價來預測通膨並不會是一個很好的選擇。

過上述這幾個步驟,我們可以透過基本的經濟學理論以及邏輯來檢測出我們需要的預測變數,並且為其提供一個可靠的資料作為佐證。而有了這樣的理論基礎以後以及資料佐證以後,就能夠決定我們要使用那些變數放到VAR裡面作為預測通膨的工具。

用Python來寫VAR

總體經濟學家往往預測的模型裡面都是動不動就上百個變數,而我們這邊為了方面起見,就用6個變數就好,除了上述的:失業率、個人實質所得以及利率以外,我們多加進去新屋開工以及經過外匯調整過後的美元指數進去預測通膨。

以數學來講,我們的模型會如同下圖一般,由上到下分別是通膨、個人實質所得、失業率、聯準會利率、美元指數以及新屋開工:

而在使用VAR的時候,經濟學家比較偏好資料是stationary的,所謂的stationary指的是資料本身的是均質回歸,也就是你看到資料的時候應該會是上下波動起伏而不是單純的一直往上衝。而許多總體經濟的資料一開始拿到的都不是stationary,但是你如果把上一期跟這一期的資料相減,尤其是取log以後相減,往往都會變成stationary,而這其實是有數學公式可以證明的,但是這邊就不細談了。

下面幾行的code主要就是先把資料叫出來,我取12個時間差,並且試圖先畫圖出來看看資料是否stationary,而寫出來以後我們會得到圖三的結果,上面那一排是原本資料的歷史走勢,下面則是你取時間差以後的走勢,可以看到如果不把2020的資料算進去的話,其實你取了時間差以後往往都是stationary的。

from statsmodels.tsa.api import VAR

fred = pd.read_csv('https://s3.amazonaws.com/files.fred.stlouisfed.org/fred-md/monthly/current.csv')

fred = fred.drop(0)
fred["sasdate"] = pd.to_datetime(fred["sasdate"])
fred = fred.rename(columns={"sasdate": "date"})
fred = fred.set_index("date")

for c in list(fred.columns.values):
    fred[c + "_diff"] = fred[c] - fred[c].shift(12)

new_names = ["cpi", "rpi", "unemp", "fedrate", "usd", "houst"]
old_names = ["CPIAUCSL", "RPI", "UNRATE", "FEDFUNDS", "TWEXAFEGSMTHx", "HOUST"]

for n,o in dict(zip(new_names, old_names)).items():
    fred = fred.rename(columns={o:n})
    
new_names_diff = ["cpi_diff", "rpi_diff", "unemp_diff", "fedrate_diff", "usd_diff", "houst_diff"]
old_names_diff = ["CPIAUCSL_diff", "RPI_diff", "UNRATE_diff", "FEDFUNDS_diff", "TWEXAFEGSMTHx_diff", "HOUST_diff"]

for nd,od in dict(zip(new_names_diff, old_names_diff)).items():
    fred = fred.rename(columns={od:nd})
def plot_vars(data, levels, color, leveltype):
    
    """
    Displays historical trends of VAR variables
    And see if it's sensible to just select levels instead of differences
    """
    
    fig, ax = plt.subplots(1, 6, figsize=(16,2.5), sharex=True)
    
    palettes = ["blue", "green", "red", "orange", "purple", "black"]
    
    for col, i in dict(zip(levels, list(range(6)))).items():
        data[col].plot(ax=ax[i], legend=True, linewidth=1.0, color=color, sharex=True)     
    
    fig.set_facecolor("floralwhite")
    fig.suptitle(f"Historical trends of VAR {leveltype} variables", 
                 fontsize=14, fontweight="bold", fontname="Verdana")

plot_vars(fred, levels=new_names, color="royalblue", leveltype="levels")
plot_vars(fred, levels=new_names_diff, color="firebrick", leveltype="difference")
圖三:上排是原始資料,下排是取時間差的資料,由左到右分別是:通膨、個人實質所得、失業率、聯準會利率、美元指數以及新屋開工

而正如我所說VAR的使用上面偏好資料是stationary,因此我選擇在這個案例裡面使用到2019/12/30的資料就好,至於要怎麼處理2020那個詭異的數字?那就是目前NBER的總體經濟學家正討論中的議題,有機會再來另外說。

而接著下面的code就是來寫VAR模型的,我們使用1973年1月到2009年一月的資料作為訓練樣本,來看看跟130個月份以後的,基本上算是寫一個VAR模型的基本,值得注意的是最後一行的指令是要計算mean square error(MSE),基本是計算的方式就是(預測數據-真實數據)^2,也就是來衡量到底這個模型的預測能力怎樣,從圖五可以看到預測誤差是6.8,也就是平均而言大概會會偏離2.2%,是個預測能力其實不怎麼樣的模型。

start_date = "2009-02-01"
end_date = "2020-01-01"

def var_create(columns, data):
    
    """
    Creates vector autoregressive model given data and list of selected variables
    Returns the MSE between forecasted and actual values
    Also returns the concatenated dataset for visualization purposes
    """
    
    data = data[columns]
    data = data.dropna(axis=0)
    data.index.to_period("M")
    
    # Split dataset and run VAR on the trained part
    data_train = data.loc["1973-01":"2009-01", :]
    var_train = VAR(data_train)
    results = var_train.fit(12)
    lag_order = results.k_ar
    forecasted = pd.DataFrame(results.forecast(data_train.values[-lag_order:], 130)) # Forecast 130 months
    
    # Rename forecasted columns
    forecasted_names = list(forecasted.columns.values)
    data_train_names = list(data_train.columns.values)
    
    var_dict = dict(zip(forecasted_names, data_train_names))
    
    for f,t in var_dict.items():
        forecasted = forecasted.rename(columns={f:t + "_fcast"})
        
    forecasted.index = pd.date_range(start=start_date, periods=forecasted.shape[0], freq="MS")
    forecasted.index.names = ["date"]
    
    # Parse together forecasted data with original dataset
    final_data = pd.merge(forecasted, data, left_index=True, right_index=True)
    final_data = final_data.sort_index(axis=0, ascending=True)
    final_data = pd.concat([data_train, final_data], sort=True, axis=0)
    final_data = final_data.sort_index(axis=0, ascending=True)
    
    var_mse = metrics.mean_squared_error(final_data.loc[start_date:end_date,"cpi_diff_fcast"], 
                           final_data.loc[start_date:end_date,"cpi_diff"])
    
    return var_mse, final_data
mse1, df1 = var_create(columns=["unemp", "cpi_diff", "fedrate_diff", 
                    "usd_diff", "rpi_diff", "houst"], data=fred)
print(f"The mean squared error between the forecasted and actual values is {mse1}")
圖五:預測誤差結果

但是,我們來視覺化一下到底真實的預測結果如何,下面的code可以幫助我們做到這一點並且產出圖六的結果,可以看到事實上在趨勢上面算是抓得準確的,之所以預測誤差較大的原因在於並不能準去的抓到一些時間的劇烈波動性,但視覺上來看算是還可以用的模型。

def plot_cpi(final_data, var_mse, approach):
    
    """
    Plots the actual values against forecast
    """
    
    fig, ax = plt.subplots(figsize=(14,6))
    colors = sns.color_palette("deep", 8)

    final_data["cpi_diff_fcast"].plot(ax=ax, legend=True, linewidth=2.5, linestyle="dashed")
    final_data["cpi_diff"].plot(ax=ax, legend=True, alpha=0.6, linestyle="solid")
    
    if approach=="traditional":
        ax.set_title("VAR in-sample forecast, traditional approach", fontsize=16, 
                     fontweight="bold", fontname="Verdana", loc="left")
    elif approach=="lasso":
        ax.set_title("VAR in-sample forecast, Lasso approach", fontsize=16, 
                     fontweight="bold", fontname="Verdana", loc="left")
    elif approach=="XGBoost":
        ax.set_title("VAR in-sample forecast, XGBoost approach", fontsize=16, 
                     fontweight="bold", fontname="Verdana", loc="left")
    else:
        ax.set_title("VAR in-sample forecast", fontsize=16, 
                     fontweight="bold", fontname="Verdana", loc="left")
    
    ax.set_ylabel("First differences", fontname="Verdana")
    ax.legend([f"VAR Forecast, MSE={var_mse}", "CPI Real Values"])
plot_cpi(final_data=df1, var_mse=mse1, approach="traditional")
圖六:傳統VAR的預測結果

接下來,我們來使用兩個在經濟學裡面很熱門的機器學習模型:Lasso 以及XGBoost,Lasso算是一種透過正向回饋不斷減少預測誤差的方法,而XGBoot則是一種tree model,兩者都還算是熱門的方法,而我們來測試看看這兩種方法選擇的變數是否真的比起經濟學理論例如菲利浦曲線或者是歐肯定率更好用,見證一下到底機器學習是否打敗經濟學理論。

Lasso選擇總經變數

下面的code來解釋一下,第一段主要是來製造我們的訓練樣本,這邊是以2009年1月1日作為最後的日期,而拿這以前的數據來當作我們的訓練樣本,試圖來去fit2009年1月1日以後的資料,第二段則是讓Lasso自己幫我們找出適合來預估CPI的相關總經資料,於是定義x以及y,並讓Lasso選擇x,最後第三段則是來跑Lasso,而結果呈現在圖七。

def timeseries_train_test_split(X, y, testsize):
    """
    This function splits the sample into a trained and test data
    """
    
    X_train = X.loc[:"2009-01-01"] # Until a particular date
    y_train = y.loc[:"2009-01-01"]
    X_test = X.loc["2009-01-01":]
    y_test = y.loc["2009-01-01":]
    
    return X_train, y_train, X_test, y_test

def train_test_plot(model, X_train, X_test):
    """
    This will plot the actual values of CPI against the one fitted by the model
    We train the model until 2009 and then use it from 2009 onwards on the test features dataset
    """
    fig, ax = plt.subplots(figsize=(12,4))
    colors = sns.color_palette("deep", 8)
    
    yvalues = pd.DataFrame(y_test)
    
    forecasted = list(model.predict(X_test)) # Use the model fit on features data from 2009 onwards
    df_fcast = pd.DataFrame({"date": list(yvalues.index), "cpi_fcast": forecasted})
    df_fcast = df_fcast.set_index("date")
    
    df = pd.merge(yvalues, df_fcast, left_index=True, right_index=True)

    df["cpi_fcast"].plot(ax=ax, legend=True, linewidth=2.5, linestyle="dashed", color="forestgreen") # CPI fitted
    df["cpi_diff"].plot(ax=ax, legend=True, linewidth=1.5, linestyle="solid", color="salmon") # Actual CPI values
    
    ax.set_title("CPI vs. Model's CPI")
    ax.set_ylabel("First differences")
    ax.legend(["Fitted CPI","Actual CPI"])
cpi_target = fred.dropna().cpi_diff
fred_features = fred.dropna().drop(["cpi_diff"], axis=1)
X_train, y_train, X_test, y_test = timeseries_train_test_split(X=fred_features, y=cpi_target, testsize=0.25)

lasso = linear_model.LassoCV(cv=model_selection.TimeSeriesSplit(n_splits=5), 
                             alphas=None, tol = 10000, normalize=True) 

fred_lasso = lasso.fit(X_train, y_train)
optimal_alpha = fred_lasso.alpha_

lasso2 = linear_model.Lasso(alpha=optimal_alpha, normalize=True)
lasso2.fit(X_train, y_train)

train_test_plot(lasso2, X_train, X_test) 
圖七:Lasso預測CPI的準確

你可以從圖七看到,Lasso的預測能力根本屌打經濟學家用的VAR,同樣我用下面的code來叫做預測誤差,也可以看到預測誤差小的神奇,於是我就很懷疑到底他是用什麼總經數據來預測,於是下一段的code是把它用啥數據的叫出來,結果呈現在圖九,而根據原始資料去對應,他拿的是除了醫療費用以外的CPI、服務業CPI、商品CPI、食物以外的CPI,房價以外的CPI以及醫療的CPI來做為前面主要的預測,但這根本不太合理,基本上只是把CPI的小分類來做為預測CPI的工具,絕對會產生多重共線性(multicollinearity)的問題。

metrics.mean_squared_error(y_test, lasso2.predict(X_test))
圖八:Lasso預測誤差
lasso_coefs = pd.DataFrame({"features":list(X_train), "coef": lasso2.coef_})
lasso_coefs = lasso_coefs[lasso_coefs.coef != 0.0]
lasso_coefs.sort_values("coef", ascending=False)

圖九:Lasso選擇的總經變數:醫療費用以外的CPI、服務業CPI、商品CPI、食物以外的CPI,房價以外的CPI以及醫療的CPI(由上到下)

而如果Lasso選擇的這些變數真的厲害,我拿去跑VAR應該也可以,於是我用下面的code把這些變數放進去VAR裡面,可以看到預測誤差其實還比傳統經濟學理論還差,而且視覺化也不比傳統經濟理論好到那裡去

old_names2 = ["CUSR0000SA0L5_diff", "CUSR0000SAS_diff", 
              "CUSR0000SAC_diff", "CPIULFSL_diff", "CUSR0000SA0L2_diff"]

new_names2 = ["cpi_lessmed", "cpi_serv", "cpi_comm", "cpi_lessfd", "cpi_lessshelt"]

for n,o in dict(zip(new_names2, old_names2)).items():
    fred = fred.rename(columns={o:n})

new_names2 = new_names2 + ["cpi_diff"]
mse2, df2 = var_create(columns=new_names2, data=fred)
print(f"The mean squared error between the forecasted and actual values is {mse2}")
plot_cpi(final_data=df2, var_mse=mse2, approach="lasso")
圖十:Lasso在VAR的預測誤差
圖十一:Lasso在VAR的結果

XGBoost選擇總經變數

XGBoost是一種透過最優化梯度的決策樹演算法,也算是熱門的選擇之一,如同Lasso一樣,但值得注意的是XGBoost以及其他的橘測樹演算法例如random forest之類的對於non-stationary的資料處理能力其實很糟糕,於是先把那些non-stationary的資料給剔除,第一段的code就是在做這件事情,如果不相信的話可以試著把第一段第一行的給刪掉重新自己跑一次,而第二段的code則是XGBoost的code:

fred_features = fred_features[list(fred_features.filter(regex = "_diff"))] # Drop non-stationary variables 
X_train, y_train, X_test, y_test = timeseries_train_test_split(X=fred_features, y=cpi_target, testsize=0.25)
scaler = preprocessing.StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)

xgb = XGBRegressor()
xgb.fit(X_train_scaled, y_train)

X_test_scaled = scaler.transform(X_test)

train_test_plot(model=xgb, X_train=X_train_scaled, X_test=X_test_scaled)
圖十二:XGBoost的預測能力

圖十二式XGboost的結果,可以看到除了2015左右一小段的預測以外,基本上XGBoost的預測能力也還算不錯,於是我們先來看看那些物件是比較重要的,下面第一段的code就是來看哪一些物件比較重要,而第二段ˊ設找出這些物件,而我們可以根據原始的資料使用手冊找到這些分別是:個人實質所得、個人實質消費、工業產出能源花費、最終財的生產者物價、都市的消費者物價,雖然這看起來稍微可靠了一點,但後面那兩個物價只是還是有點風險。

fig_xgb, ax_xgb = plt.subplots(figsize=(10,6))
plot_importance(xgb, max_num_features=5, ax=ax_xgb)
for i, name in dict(zip(list(range(len(X_train.columns))), list(X_train.columns.values))).items():
    if i == 0:
        print(f"Feature {i} is {name}")
    elif i == 113:
        print(f"Feature {i} is {name}")
    elif i == 99:
        print(f"Feature {i} is {name}")
    elif i == 2:
        print(f"Feature {i} is {name}")
    elif i == 16:
        print(f"Feature {i} is {name}")
圖十三:XGBoost選出來的預測變數:個人實質所得、個人實質消費、工業產出能源花費、最終財的生產者物價、都市的消費者物價(由上到下)

接著,我們來重新命名這些變數,而有些已經命名過的就不用動了,接著來計算XGBoost選擇的總經變數在VAR跑出來的預測誤差以及視覺上面的結果,而如同圖十四以及圖十五可以看到的,預測誤差以及結果其實是很離譜的。

old_names3 = ["DPCERA3M086SBEA_diff", "IPB51222S_diff", 
              "WPSFD49207_diff", "rpi_diff", "cpi_lessmed"]

new_names3 = ["pce_diff", "ip_utility_diff", "ppi_fg_diff", "rpi_diff", "cpi_lessmed"]

for n,o in dict(zip(new_names3, old_names3)).items():
    fred = fred.rename(columns={o:n})

new_names3 = new_names3 + ["cpi_diff"]
mse3, df3 = var_create(columns=new_names3, data=fred)
print(f"The mean squared error between the forecasted and actual values is {mse3}")
plot_cpi(final_data=df3, var_mse=mse3, approach="XGBoost")
圖十四:XGBoost在VAR的預測誤差
圖十五:XGboost在VAR的預測誤差呈現

結論:Overfitting以及知識

事實上VAR本身也是很糟糕的預測方式,而創始者Christopher Sims後來則是在2007年的這篇”Bayesian Methods in Applied Econometrics, or, Why Econometrics Should Always and Everywhere Be Bayesian“說要使用Bayesian VAR,而其他的例如Sturctural VAR,Markov-Switching VAR,q-VAR等等都有各自的發展使得VAR更完善,而VAR也成為了許多總經學家認為可以作為baseline卻不能精準預測的底線。

我並不是要教你怎麼預測,而是要跟你說使用機器學習的風險,其他的科目我並不清楚,但是在經濟學裏面,透過這篇文章我想可以看出來當你要預測經濟資料(甚至是股市,金融市場),你如果缺乏相關知識,單純依賴機器學習的方法,有可能出現問題。這個問題在統計學裡面稱為overfitting,也就是你的方法固然很好,但只有在那個方法裡面好而已,例如你可以Lasso以及XGBoost本身都有很好的預測能力,但是你把它選出來的變數丟去其他更基本的模型就會發現其實預測能力很弱。

某方面而言,這就是計量經濟學以及機器學習有時候會合不來的地方,在計量經濟裡面,無論是總體或者個體,都講求證明的有效以及嚴謹,而不能只給一個預測能力高的方法,因此發表在頂級計量經濟相關的文章都需要長久的證明,而不能只是黑貓白貓,只要能抓老鼠的就是好貓,因為你不知道,這隻能抓老鼠的貓是不是只是剛好適合抓一定區域的老鼠,如果用這頭只會抓一定區域老鼠的貓來作為工具,當遇到不同老鼠的時候,你只會養一頭不幹事的加菲貓而已。

2 thoughts on “為什麼需要經濟理論來預測經濟趨勢:比較機器學習與計量經濟

  1. 你好
    想請問關於這裡的比較方法
    我沒有理解錯誤的話,應該是比較下三種的表現:
    1. 透過經濟理論挑選變數,透過VAR預測
    2. 先利用Lasso挑選變數,再交由VAR預測
    3. 先利用XGBoost挑選變數,再交由VAR預測

    那這樣看來我想到的問題是:

    (1) 為何要堅持使用VAR去做最後的預測?
    既然原本就已經透過Lasso/XGBoost建立模型了
    那為什麼不直接沿用就好,反而要丟回去VAR做預測
    我個人的直覺感覺這樣做有點奇怪,畢竟三個模型的運作方法、線性與否都不太一樣
    這樣變數是可以通用的嗎?
    文內有提到「如果Lasso選擇的這些變數真的厲害,我拿去跑VAR應該也可以」
    這點我感覺有點奇怪,A模型的機制下篩出來的變數直接放到B模型應該本來就不一定可以運作良好吧(?

    因此對跑出來的這個結果,我自己的理解是:它說不定只是反映了模型間的差異程度
    所以若某模型跟VAR的差異很大,那這個模型篩出來的變數丟進VAR就容易出事
    VAR是多變量版本的AR,而AR也有點接近一般統計中的線性迴歸模型(應該是吧我時序分析有點忘了…)
    另外Lasso是線性迴歸加入regularization項的結果
    因此兩者的模型原裡依然存在一點相似程度
    至於XGBoost的原理則跟Lasso或VAR的差異更大
    所以最終也反映了「XGBoost篩出的變數放進VAR的誤差」大於「Lasso篩出的變數放進VAR的誤差」
    (另外一點是從文中看來,LASSO得到的變數有點像作弊的感覺?)

    不過這只是我看到這個結果的直覺想法啦
    但是我不懂經濟也不是很懂機器學習,不知道以你的角度來看這樣是否合理

    (2) 那如果直接以Lasso和XGBoost下去做之後的預測,表現也會差於VAR嗎?
    這算是承接上一個問題
    Lasso和XGBoost的變數丟回VAR表現會不佳
    那就不要回去VAR,直接沿用Lasso/XGBoost去做預測呢?

    (3) 關於overfiitting
    我不確定是否我對overfitting有所誤解
    但是我自己個人理解的overfitting應該是模型太過於複雜(超過它應該要有的複雜度),導致在trainning data表現良好,但是在test data/validation data中表現較差
    而這應該只能跟自己比較才對?
    也就是如果要說Lasso/XGBoost有出現overfitting
    那應該是發現到Lasso/XGBoost在trainning data的誤差很小,同樣的Lasso模型在test data卻有很大的誤差
    而不是看Lasso篩的變數丟回較基本的模型的誤差量值(?

    不然假設有個真實的數據是來自於 y = exp(1 + x1 + x2 + x3^2)
    最後發現一個線性模型A表現不錯:y = exp(1 + x1 + x2 + x3)
    然後把這個A模型的變數丟到更基本的線性迴歸:y = 1 + x1 + x2 + x3
    結果發現表現很差,進而說模型A有overfitting
    這樣感覺就有點奇怪,畢竟真實數據背後的生成機制就沒有這麼簡單,那太過簡單的模型當然表現不好

    那我想到的大概是這樣,不知道我打的夠不夠清楚會不會看不懂QQ
    算是看完之後的一點疑惑,不過我自己不是經濟背景+對機器學習也不熟,所以不知道這些想法是否有問題
    再麻煩版主了QQ
    謝謝!

    1. (1)我必須先說,我個人的專長是計量經濟以及總體經濟,機器學習並非我的專長,所以可能有誤也不一定,而我對於機器學習的之是大部分來自於Bruce Hansen的這本”Econometrics”第29章以及Athey and Imbens(2019)”Machine Learning Methods
      That Economists Should Know About”,所以我可能對於機器學習有誤會也不一定,我基本上都是把機器學習當成另一種統計工具看是否能提供更好的經濟學解釋。

      之所以用VAR,是因為VAR本身是總體經濟最常來使用來解釋變數的工具,而假設你想要驗證你選出來的變數是否符合一個總體經濟的概念,那使用VAR是總經學術最常使用的方法,諾貝爾獎得主Chris Sims獲獎的理由之一就是證明為何總經資料由於有著趨勢(trend)以及協整(cointergration)於是使用VAR會是個比較可信的工具。假設你選出來的變數根本不能解釋你想要預測的結果(例如通膨),那無論你的工具多麼的神奇,以經濟學家的觀點來講都是值得懷疑的(當然,機器學習的人可能有不一樣的觀點)。

      (2)承上,Lasso以及XGBoost主要目的在於選擇解釋的變數,但是VAR裡面的變數則是根據經濟理論而來的,你把VAR裡面的變數放進去這兩者裡面你只是在用這兩個模型透過不同的loss functuion來選擇這些變數裡面比較好的變數,但基本上的性質還是線性回歸。

      (3)我對於over-fitting的理解是如果你選出來的變數因為太過複雜的模型而看起來有很強大的解釋能力但是實際上解釋能力薄弱的話,就稱為over-fitting(例如Varian(2014)Big Data: New Tricks for Econometrics或者Clark(2004)Can Out-of-Sample Forecast ComparisonsHelp Prevent Overfitting?),當然,如果要統計上面檢視的話是把Lasso/XGBoost去做out-of-sample test來去看你變數的解釋能力如何,但假設機器學習選出來的變數根本無法提供任何有效的總體經濟學理論上的解釋,那麼認為這兩個模型選出來的變數有over-fitting應該很正常。

      當然,我應不是機器學習的專家而是從計量經濟的角度來解釋的,很可能有誤,還多見諒。

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *