女人自慰AV免费观看内涵网,日韩国产剧情在线观看网址,神马电影网特片网,最新一级电影欧美,在线观看亚洲欧美日韩,黄色视频在线播放免费观看,ABO涨奶期羡澄,第一导航fulione,美女主播操b

0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫(xiě)文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

分析和預(yù)測(cè)時(shí)序數(shù)據(jù)的主要方法,如何使用Python處理時(shí)序數(shù)據(jù)

zhKF_jqr_AI ? 來(lái)源:未知 ? 作者:李倩 ? 2018-09-04 08:45 ? 次閱讀

Zeptolab數(shù)據(jù)科學(xué)家Dmitriy Sergeev介紹了分析和預(yù)測(cè)時(shí)序數(shù)據(jù)的主要方法。

大家好!

這次的開(kāi)放機(jī)器學(xué)習(xí)課程的內(nèi)容是時(shí)序數(shù)據(jù)。

我們將查看如何使用Python處理時(shí)序數(shù)據(jù),哪些方法和模型可以用來(lái)預(yù)測(cè);什么是雙指數(shù)平滑和三指數(shù)平滑;如果平穩(wěn)(stationarity)不是你的菜,該怎么辦;如何創(chuàng)建SARIMA并且活下來(lái);如何使用XGBoost做出預(yù)測(cè)。所有這些都將應(yīng)用于(嚴(yán)酷的)真實(shí)世界例子。

導(dǎo)言

在我的工作中,我?guī)缀趺刻於紩?huì)碰到和時(shí)序有關(guān)的任務(wù)。最頻繁的問(wèn)題是——明天/下一周/下個(gè)月/等等,我們的指標(biāo)將是什么樣——有多少玩家會(huì)安裝應(yīng)用,他們的在線時(shí)長(zhǎng)會(huì)是多少,他們會(huì)進(jìn)行多少次操作,取決于預(yù)測(cè)所需的質(zhì)量,預(yù)測(cè)周期的長(zhǎng)度,以及時(shí)刻,我們需要選擇特征,調(diào)整參數(shù),以取得所需結(jié)果。

基本定義

時(shí)序的簡(jiǎn)單定義:

時(shí)序——一系列以時(shí)間順序?yàn)?a target="_blank">索引(或列出、繪出)的數(shù)據(jù)點(diǎn)。

因此,數(shù)據(jù)以相對(duì)確定的時(shí)刻組織。所以,和隨機(jī)樣本相比,可能包含我們將嘗試提取的額外信息。

讓我們導(dǎo)入一些庫(kù)。首先我們需要statsmodels庫(kù),它包含了一大堆統(tǒng)計(jì)學(xué)建模函數(shù),包括時(shí)序。對(duì)不得不遷移到Python的R粉來(lái)說(shuō),絕對(duì)會(huì)感到statsmodels很熟悉,因?yàn)樗С诸愃芖age ~ Age + Education這樣的模型定義。

import numpy as np # 向量和矩陣

import pandas as pd # 表格和數(shù)據(jù)處理

import matplotlib.pyplot as plt # 繪圖

import seaborn as sns # 更多繪圖功能

from dateutil.relativedelta import relativedelta # 處理不同格式的時(shí)間日期

from scipy.optimize import minimize # 最小化函數(shù)

import statsmodels.formula.api as smf # 統(tǒng)計(jì)學(xué)和計(jì)量經(jīng)濟(jì)學(xué)

import statsmodels.tsa.api as smt

import statsmodels.api as sm

import scipy.stats as scs

from itertools import product # 一些有用的函數(shù)

from tqdm import tqdm_notebook

import warnings # 勿擾模式

warnings.filterwarnings('ignore')

%matplotlib inline

作為例子,讓我們使用一些真實(shí)的手游數(shù)據(jù),玩家每小時(shí)觀看的廣告,以及每天游戲幣的花費(fèi):

ads = pd.read_csv('../../data/ads.csv', index_col=['Time'], parse_dates=['Time'])

currency = pd.read_csv('../../data/currency.csv', index_col=['Time'], parse_dates=['Time'])

plt.figure(figsize=(15, 7))

plt.plot(ads.Ads)

plt.title('Ads watched (hourly data)')

plt.grid(True)

plt.show()

plt.figure(figsize=(15, 7))

plt.plot(currency.GEMS_GEMS_SPENT)

plt.title('In-game currency spent (daily data)')

plt.grid(True)

plt.show()

預(yù)測(cè)質(zhì)量指標(biāo)

在實(shí)際開(kāi)始預(yù)測(cè)之前,先讓我們理解下如何衡量預(yù)測(cè)的質(zhì)量,查看下最常見(jiàn)、使用最廣泛的測(cè)度:

R2,決定系數(shù)(在經(jīng)濟(jì)學(xué)中,可以理解為模型能夠解釋的方差比例),(-inf, 1]sklearn.metrics.r2_score

平均絕對(duì)誤差(Mean Absolute Error),這是一個(gè)易于解釋的測(cè)度,因?yàn)樗挠?jì)量單位和初始序列相同,[0, +inf)sklearn.metrics.mean_absolute_error

中位絕對(duì)誤差(Median Absolute Error),同樣是一個(gè)易于解釋的測(cè)度,對(duì)離群值的魯棒性很好,[0, +inf)sklearn.metrics.median_absolute_error

均方誤差(Mean Squared Error),最常用的測(cè)度,給較大的錯(cuò)誤更高的懲罰,[0, +inf)sklearn.metrics.mean_squared_error

均方對(duì)數(shù)誤差(Mean Squared Logarithmic Error),和MSE差不多,只不過(guò)先對(duì)序列取對(duì)數(shù),因此能夠照顧到較小的錯(cuò)誤,通常用于具有指數(shù)趨勢(shì)的數(shù)據(jù),[0, +inf)sklearn.metrics.mean_squared_log_error

平均絕對(duì)百分誤差(Mean Absolute Percentage Error),類似MAE不過(guò)基于百分比——當(dāng)你需要向管理層解釋模型的質(zhì)量時(shí)很方便——[0, +inf),sklearn中沒(méi)有實(shí)現(xiàn)。

# 引入上面提到的所有測(cè)度

from sklearn.metrics import r2_score, median_absolute_error, mean_absolute_error

from sklearn.metrics import median_absolute_error, mean_squared_error, mean_squared_log_error

# 自行實(shí)現(xiàn)sklearn沒(méi)有提供的平均絕對(duì)百分誤差很容易

def mean_absolute_percentage_error(y_true, y_pred):

return np.mean(np.abs((y_true - y_pred) / y_true)) * 100

棒極了,現(xiàn)在我們知道如何測(cè)量預(yù)測(cè)的質(zhì)量了,可以使用哪些測(cè)度,以及如何向老板翻譯結(jié)果。剩下的就是創(chuàng)建模型了。

平移、平滑、評(píng)估

讓我們從一個(gè)樸素的假設(shè)開(kāi)始——“明天會(huì)和今天一樣”,但是我們并不使用類似y^t=y(t-1)這樣的模型(這其實(shí)是一個(gè)適用于任意時(shí)序預(yù)測(cè)問(wèn)題的很好的基線,有時(shí)任何模型都無(wú)法戰(zhàn)勝這一模型),相反,我們將假定變量未來(lái)的值取決于前n個(gè)值的平均,所以我們將使用的是移動(dòng)平均(moving average)。

def moving_average(series, n):

"""

計(jì)算前n項(xiàng)觀測(cè)的平均數(shù)

"""

return np.average(series[-n:])

# 根據(jù)前24小時(shí)的數(shù)據(jù)預(yù)測(cè)

moving_average(ads, 24)

結(jié)果:116805.0

不幸的是這樣我們無(wú)法做出長(zhǎng)期預(yù)測(cè)——為了預(yù)測(cè)下一步的數(shù)據(jù)我們需要實(shí)際觀測(cè)的之前的數(shù)據(jù)。不過(guò)移動(dòng)平均還有一種用途——平滑原時(shí)序以顯示趨勢(shì)。pandas提供了實(shí)現(xiàn)DataFrame.rolling(window).mean()。窗口越寬,趨勢(shì)就越平滑。遇到噪聲很大的數(shù)據(jù)時(shí)(財(cái)經(jīng)數(shù)據(jù)十分常見(jiàn)),這一過(guò)程有助于偵測(cè)常見(jiàn)模式。

def plotMovingAverage(series, window, plot_intervals=False, scale=1.96, plot_anomalies=False):

"""

series - 時(shí)序dateframe

window - 滑窗大小

plot_intervals - 顯示置信區(qū)間

plot_anomalies - 顯示異常值

"""

rolling_mean = series.rolling(window=window).mean()

plt.figure(figsize=(15,5))

plt.title("Moving average window size = {}".format(window))

plt.plot(rolling_mean, "g", label="Rolling mean trend")

# 繪制平滑后的數(shù)據(jù)的置信區(qū)間

if plot_intervals:

mae = mean_absolute_error(series[window:], rolling_mean[window:])

deviation = np.std(series[window:] - rolling_mean[window:])

lower_bond = rolling_mean - (mae + scale * deviation)

upper_bond = rolling_mean + (mae + scale * deviation)

plt.plot(upper_bond, "r--", label="Upper Bond / Lower Bond")

plt.plot(lower_bond, "r--")

# 得到區(qū)間后,找出異常值

if plot_anomalies:

anomalies = pd.DataFrame(index=series.index, columns=series.columns)

anomalies[series

anomalies[series>upper_bond] = series[series>upper_bond]

plt.plot(anomalies, "ro", markersize=10)

plt.plot(series[window:], label="Actual values")

plt.legend(loc="upper left")

plt.grid(True)

平滑(窗口大小為4小時(shí)):

plotMovingAverage(ads, 4)

平滑(窗口大小為12小時(shí)):

plotMovingAverage(ads, 12)

平滑(窗口大小為24小時(shí)):

plotMovingAverage(ads, 24)

如你所見(jiàn),在小時(shí)數(shù)據(jù)上按日平滑讓我們可以清楚地看到瀏覽廣告的趨勢(shì)。周末數(shù)值較高(周末是娛樂(lè)時(shí)間),工作日一般數(shù)值較低。

我們可以同時(shí)繪制平滑值的置信區(qū)間:

plotMovingAverage(ads, 4, plot_intervals=True)

現(xiàn)在讓我們?cè)谝苿?dòng)平均的幫助下創(chuàng)建一個(gè)簡(jiǎn)單的異常檢測(cè)系統(tǒng)。不幸的是,在這段時(shí)序數(shù)據(jù)中,一切都比較正常,所以讓我們故意弄出點(diǎn)異常來(lái):

ads_anomaly = ads.copy()

# 例如廣告瀏覽量下降了20%

ads_anomaly.iloc[-20] = ads_anomaly.iloc[-20] * 0.2

讓我們看看這個(gè)簡(jiǎn)單的方法能不能捕獲異常:

plotMovingAverage(ads_anomaly, 4, plot_intervals=True, plot_anomalies=True)

酷!按周平滑呢?

plotMovingAverage(currency, 7, plot_intervals=True, plot_anomalies=True)

不好!這是簡(jiǎn)單方法的缺陷——它沒(méi)能捕捉月度數(shù)據(jù)的季節(jié)性,幾乎將所有30天出現(xiàn)一次的峰值當(dāng)作異常值。如果你不想有這么多虛假警報(bào),最好考慮更復(fù)雜的模型。

順便提下移動(dòng)平均的一個(gè)簡(jiǎn)單修正——加權(quán)平均(weighted average)。其中不同的觀測(cè)具有不同的權(quán)重,所有權(quán)重之和為一。通常最近的觀測(cè)具有較高的權(quán)重。

def weighted_average(series, weights):

result = 0.0

weights.reverse()

for n in range(len(weights)):

result += series.iloc[-n-1] * weights[n]

return float(result)

weighted_average(ads, [0.6, 0.3, 0.1])

結(jié)果:98423.0

指數(shù)平滑

那么,如果我們不只加權(quán)最近的幾項(xiàng)觀測(cè),而是加權(quán)全部現(xiàn)有的觀測(cè),但對(duì)歷史數(shù)據(jù)的權(quán)重應(yīng)用指數(shù)下降呢?

這一模型的值是當(dāng)前觀測(cè)和歷史觀測(cè)的加權(quán)平均。權(quán)重α稱為平滑因子,定義多快“遺忘”之前的觀測(cè)。α越小,之前的值的影響就越大,序列就越平滑。

指數(shù)隱藏在函數(shù)的遞歸調(diào)用之中,y^t-1本身包含(1-α)y^t-2,以此類推,直到序列的開(kāi)始。

def exponential_smoothing(series, alpha):

"""

series - 時(shí)序數(shù)據(jù)集

alpha - 浮點(diǎn)數(shù),范圍[0.0, 1.0],平滑參數(shù)

"""

result = [series[0]] # 第一項(xiàng)和序列第一項(xiàng)相同

for n in range(1, len(series)):

result.append(alpha * series[n] + (1 - alpha) * result[n-1])

return result

def plotExponentialSmoothing(series, alphas):

with plt.style.context('seaborn-white'):

plt.figure(figsize=(15, 7))

for alpha in alphas:

plt.plot(exponential_smoothing(series, alpha), label="Alpha {}".format(alpha))

plt.plot(series.values, "c", label = "Actual")

plt.legend(loc="best")

plt.axis('tight')

plt.title("Exponential Smoothing")

plt.grid(True);

plotExponentialSmoothing(ads.Ads, [0.3, 0.05])

plotExponentialSmoothing(currency.GEMS_GEMS_SPENT, [0.3, 0.05])

雙指數(shù)平滑

目前我們的方法能給出的只是單個(gè)未來(lái)數(shù)據(jù)點(diǎn)的預(yù)測(cè)(以及一些良好的平滑),這很酷,但還不夠,所以讓我們擴(kuò)展下指數(shù)平滑以預(yù)測(cè)兩個(gè)未來(lái)數(shù)據(jù)點(diǎn)(當(dāng)然,同樣經(jīng)過(guò)平滑)。

序列分解應(yīng)該能幫到我們——我們得到兩個(gè)分量:截距(也叫水平)l和趨勢(shì)(也叫斜率)b。我們使用之前提到的方法學(xué)習(xí)預(yù)測(cè)截距(或期望的序列值),并將同樣的指數(shù)平滑應(yīng)用于趨勢(shì)(假定時(shí)序未來(lái)改變的方向取決于之前的加權(quán)變化)。

上面的第一個(gè)函數(shù)描述截距,和之前一樣,它取決于序列的當(dāng)前值,只不過(guò)第二項(xiàng)現(xiàn)在分成水平和趨勢(shì)兩個(gè)分量。第二個(gè)函數(shù)描述趨勢(shì),它取決于當(dāng)前一步的水平變動(dòng),以及之前的趨勢(shì)值。這里β系數(shù)是指數(shù)平滑的權(quán)重。最后的預(yù)測(cè)為模型對(duì)截距和趨勢(shì)的預(yù)測(cè)之和。

def double_exponential_smoothing(series, alpha, beta):

result = [series[0]]

for n in range(1, len(series)+1):

if n == 1:

level, trend = series[0], series[1] - series[0]

if n >= len(series):

value = result[-1]

else:

value = series[n]

last_level, level = level, alpha*value + (1-alpha)*(level+trend)

trend = beta*(level-last_level) + (1-beta)*trend

result.append(level+trend)

return result

def plotDoubleExponentialSmoothing(series, alphas, betas):

with plt.style.context('seaborn-white'):

plt.figure(figsize=(20, 8))

for alpha in alphas:

for beta in betas:

plt.plot(double_exponential_smoothing(series, alpha, beta), label="Alpha {}, beta {}".format(alpha, beta))

plt.plot(series.values, label = "Actual")

plt.legend(loc="best")

plt.axis('tight')

plt.title("Double Exponential Smoothing")

plt.grid(True)

plotDoubleExponentialSmoothing(ads.Ads, alphas=[0.9, 0.02], betas=[0.9, 0.02])

plotDoubleExponentialSmoothing(currency.GEMS_GEMS_SPENT, alphas=[0.9, 0.02], betas=[0.9, 0.02])

現(xiàn)在我們有兩個(gè)可供調(diào)節(jié)的參數(shù)——α和β。前者根據(jù)趨勢(shì)平滑序列,后者平滑趨勢(shì)本身。這兩個(gè)參數(shù)越大,最新的觀測(cè)的權(quán)重就越高,建模的序列就越不平滑。這兩個(gè)參數(shù)的組合可能產(chǎn)生非常怪異的結(jié)果,特別是手工設(shè)置時(shí)。我們很快將查看自動(dòng)選擇參數(shù)的方法,在介紹三次指數(shù)平滑之后。

Holt-Winters模型

好哇!讓我們看下一個(gè)指數(shù)平滑的變體,這次是三次指數(shù)平滑。

這一方法的思路是我們加入第三個(gè)分量——季節(jié)性。這意味著,如果我們的時(shí)序不具有季節(jié)性(我們之前的例子就不具季節(jié)性),我們就不應(yīng)該使用這一方法。模型中的季節(jié)分量將根據(jù)季節(jié)長(zhǎng)度解釋截距和趨勢(shì)上的重復(fù)波動(dòng),季節(jié)長(zhǎng)度也就是波動(dòng)重復(fù)的周期。季節(jié)中的每項(xiàng)觀測(cè)有一個(gè)單獨(dú)的分量,例如,如果季節(jié)長(zhǎng)度為7(按周計(jì)的季節(jié)),我們將有7個(gè)季節(jié)分量,每個(gè)季節(jié)分量對(duì)應(yīng)一天。

現(xiàn)在我們得到了一個(gè)新系統(tǒng):

現(xiàn)在,截?cái)嗳Q于時(shí)序的當(dāng)前值減去相應(yīng)的季節(jié)分量,趨勢(shì)沒(méi)有變動(dòng),季節(jié)分量取決于時(shí)序的當(dāng)前值減去截?cái)啵约扒耙粋€(gè)季節(jié)分量的值。注意分量在所有現(xiàn)有的季節(jié)上平滑,例如,周一分量會(huì)和其他所有周一平均。關(guān)于如何計(jì)算平均以及趨勢(shì)分量和季節(jié)分量的初始逼近,可以參考工程統(tǒng)計(jì)手冊(cè)6.4.3.5:

https://www.itl.nist.gov/div898/handbook/pmc/section4/pmc435.htm

具備季節(jié)分量后,我們可以預(yù)測(cè)任意m步未來(lái),而不是一步或兩步,非常鼓舞人心。

下面是三次指數(shù)平滑模型的代碼,也稱Holt-Winters模型,得名于發(fā)明人的姓氏——Charles Holt和他的學(xué)生Peter Winters。此外,模型中還引入了Brutlag方法,以創(chuàng)建置信區(qū)間:

其中T為季節(jié)的長(zhǎng)度,d為預(yù)測(cè)偏差。你可以參考以下論文了解這一方法的更多內(nèi)容,以及它在時(shí)序異常檢測(cè)中的應(yīng)用:

https://annals-csis.org/proceedings/2012/pliks/118.pdf

classHoltWinters:

"""

Holt-Winters模型,使用Brutlag方法檢測(cè)異常

# series - 初始時(shí)序

# slen - 季節(jié)長(zhǎng)度

# alpha, beta, gamma - Holt-Winters模型參數(shù)

# n_preds - 預(yù)測(cè)視野

# scaling_factor - 設(shè)置Brutlag方法的置信區(qū)間(通常位于2到3之間)

"""

def __init__(self, series, slen, alpha, beta, gamma, n_preds, scaling_factor=1.96):

self.series = series

self.slen = slen

self.alpha = alpha

self.beta = beta

self.gamma = gamma

self.n_preds = n_preds

self.scaling_factor = scaling_factor

def initial_trend(self):

sum = 0.0

for i in range(self.slen):

sum += float(self.series[i+self.slen] - self.series[i]) / self.slen

return sum / self.slen

def initial_seasonal_components(self):

seasonals = {}

season_averages = []

n_seasons = int(len(self.series)/self.slen)

# 計(jì)算季節(jié)平均

for j in range(n_seasons):

season_averages.append(sum(self.series[self.slen*j:self.slen*j+self.slen])/float(self.slen))

# 計(jì)算初始值

for i in range(self.slen):

sum_of_vals_over_avg = 0.0

for j in range(n_seasons):

sum_of_vals_over_avg += self.series[self.slen*j+i]-season_averages[j]

seasonals[i] = sum_of_vals_over_avg/n_seasons

return seasonals

def triple_exponential_smoothing(self):

self.result = []

self.Smooth = []

self.Season = []

self.Trend = []

self.PredictedDeviation = []

self.UpperBond = []

self.LowerBond = []

seasonals = self.initial_seasonal_components()

for i in range(len(self.series)+self.n_preds):

if i == 0: # 成分初始化

smooth = self.series[0]

trend = self.initial_trend()

self.result.append(self.series[0])

self.Smooth.append(smooth)

self.Trend.append(trend)

self.Season.append(seasonals[i%self.slen])

self.PredictedDeviation.append(0)

self.UpperBond.append(self.result[0] +

self.scaling_factor *

self.PredictedDeviation[0])

self.LowerBond.append(self.result[0] -

self.scaling_factor *

self.PredictedDeviation[0])

continue

if i >= len(self.series): # 預(yù)測(cè)

m = i - len(self.series) + 1

self.result.append((smooth + m*trend) + seasonals[i%self.slen])

# 預(yù)測(cè)時(shí)在每一步增加不確定性

self.PredictedDeviation.append(self.PredictedDeviation[-1]*1.01)

else:

val = self.series[i]

last_smooth, smooth = smooth, self.alpha*(val-seasonals[i%self.slen]) + (1-self.alpha)*(smooth+trend)

trend = self.beta * (smooth-last_smooth) + (1-self.beta)*trend

seasonals[i%self.slen] = self.gamma*(val-smooth) + (1-self.gamma)*seasonals[i%self.slen]

self.result.append(smooth+trend+seasonals[i%self.slen])

# 據(jù)Brutlag算法計(jì)算偏差

self.PredictedDeviation.append(self.gamma * np.abs(self.series[i] - self.result[i])

+ (1-self.gamma)*self.PredictedDeviation[-1])

self.UpperBond.append(self.result[-1] +

self.scaling_factor *

self.PredictedDeviation[-1])

self.LowerBond.append(self.result[-1] -

self.scaling_factor *

self.PredictedDeviation[-1])

self.Smooth.append(smooth)

self.Trend.append(trend)

self.Season.append(seasonals[i%self.slen])

時(shí)序交叉驗(yàn)證

現(xiàn)在我們?cè)搩冬F(xiàn)之前的承諾,討論下如何自動(dòng)估計(jì)模型參數(shù)。

這里沒(méi)什么不同尋常的,我們需要選擇一個(gè)適合任務(wù)的損失函數(shù),以了解模型逼近數(shù)據(jù)的程度。接著,我們通過(guò)交叉驗(yàn)證為給定的模型參數(shù)評(píng)估選擇的交叉函數(shù),計(jì)算梯度,調(diào)整模型參數(shù),等等,勇敢地下降到誤差的全局最小值。

問(wèn)題在于如何在時(shí)序數(shù)據(jù)上進(jìn)行交叉驗(yàn)證,因?yàn)椋阒溃瑫r(shí)序數(shù)據(jù)確實(shí)具有時(shí)間結(jié)構(gòu),不能在一折中隨機(jī)混合數(shù)據(jù)(不保留時(shí)間結(jié)構(gòu)),否則觀測(cè)間的所有時(shí)間相關(guān)性都會(huì)丟失。這就是為什么我們將使用技巧性更強(qiáng)的方法來(lái)優(yōu)化模型參數(shù)的原因。我不知道這個(gè)方法是否有正式的名稱,但是在CrossValidated上(在這個(gè)網(wǎng)站上你可以找到所有問(wèn)題的答案,生命、宇宙以及任何事情的終極答案除外),有人提出了“滾動(dòng)式交叉驗(yàn)證”(cross-validation on a rolling basis)這一名稱。

這一想法很簡(jiǎn)單——我們?cè)跁r(shí)序數(shù)據(jù)的一小段上訓(xùn)練模型,從時(shí)序開(kāi)始到某一時(shí)刻t,預(yù)測(cè)接下來(lái)的t+n步并計(jì)算誤差。接著擴(kuò)張訓(xùn)練樣本至t+n個(gè)值,并預(yù)測(cè)從t+n到t+2×n的數(shù)據(jù)。持續(xù)擴(kuò)張直到窮盡所有觀測(cè)。初始訓(xùn)練樣本到最后的觀測(cè)之間可以容納多少個(gè)n,我們就可以進(jìn)行多少折交叉驗(yàn)證。

了解了如何設(shè)置交叉驗(yàn)證,我們將找出Holt-Winters模型的最優(yōu)參數(shù),回憶一下,我們的廣告數(shù)據(jù)有按日季節(jié)性,所以我們有slen=24。

from sklearn.model_selection importTimeSeriesSplit

def timeseriesCVscore(params, series, loss_function=mean_squared_error, slen=24):

errors = []

values = series.values

alpha, beta, gamma = params

# 設(shè)定交叉驗(yàn)證折數(shù)

tscv = TimeSeriesSplit(n_splits=3)

for train, test in tscv.split(values):

model = HoltWinters(series=values[train], slen=slen,

alpha=alpha, beta=beta, gamma=gamma, n_preds=len(test))

model.triple_exponential_smoothing()

predictions = model.result[-len(test):]

actual = values[test]

error = loss_function(predictions, actual)

errors.append(error)

return np.mean(np.array(errors))

和其他指數(shù)平滑模型一樣,Holt-Winters模型中,平滑參數(shù)的取值范圍在0到1之間,因此我們需要選擇一種支持給模型參數(shù)添加限制的算法。我們選擇了截?cái)嗯nD共軛梯度(Truncated Newton conjugate gradient)。

data = ads.Ads[:-20] # 留置一些數(shù)據(jù)用于測(cè)試

# 初始化模型參數(shù)alpha、beta、gamma

x = [0, 0, 0]

# 最小化損失函數(shù)

opt = minimize(timeseriesCVscore, x0=x,

args=(data, mean_squared_log_error),

method="TNC", bounds = ((0, 1), (0, 1), (0, 1))

)

# 取最優(yōu)值……

alpha_final, beta_final, gamma_final = opt.x

print(alpha_final, beta_final, gamma_final)

# ……并據(jù)此訓(xùn)練模型,預(yù)測(cè)接下來(lái)50個(gè)小時(shí)的數(shù)據(jù)

model = HoltWinters(data, slen = 24,

alpha = alpha_final,

beta = beta_final,

gamma = gamma_final,

n_preds = 50, scaling_factor = 3)

model.triple_exponential_smoothing()

最優(yōu)參數(shù):

0.116526802273504540.0026776974311058520.05820973606789237

繪圖部分的代碼:

def plotHoltWinters(series, plot_intervals=False, plot_anomalies=False):

"""

series - 時(shí)序數(shù)據(jù)集

plot_intervals - 顯示置信區(qū)間

plot_anomalies - 顯示異常值

"""

plt.figure(figsize=(20, 10))

plt.plot(model.result, label = "Model")

plt.plot(series.values, label = "Actual")

error = mean_absolute_percentage_error(series.values, model.result[:len(series)])

plt.title("Mean Absolute Percentage Error: {0:.2f}%".format(error))

if plot_anomalies:

anomalies = np.array([np.NaN]*len(series))

anomalies[series.values

series.values[series.values

anomalies[series.values>model.UpperBond[:len(series)]] =

series.values[series.values>model.UpperBond[:len(series)]]

plt.plot(anomalies, "o", markersize=10, label = "Anomalies")

if plot_intervals:

plt.plot(model.UpperBond, "r--", alpha=0.5, label = "Up/Low confidence")

plt.plot(model.LowerBond, "r--", alpha=0.5)

plt.fill_between(x=range(0,len(model.result)), y1=model.UpperBond,

y2=model.LowerBond, alpha=0.2, color = "grey")

plt.vlines(len(series), ymin=min(model.LowerBond), ymax=max(model.UpperBond), linestyles='dashed')

plt.axvspan(len(series)-20, len(model.result), alpha=0.3, color='lightgrey')

plt.grid(True)

plt.axis('tight')

plt.legend(loc="best", fontsize=13);

plotHoltWinters(ads.Ads)

plotHoltWinters(ads.Ads, plot_intervals=True, plot_anomalies=True)

上面的圖形表明,我們的模型能夠很好地逼近初始時(shí)序,捕捉每日季節(jié)性,總體的下降趨勢(shì),甚至一些異常。如果我們查看下建模偏差(見(jiàn)下圖),我們將很明顯地看到,模型對(duì)序列結(jié)構(gòu)的改變反應(yīng)相當(dāng)鮮明,但接著很快偏差就回歸正常值,“遺忘”了過(guò)去。模型的這一特性讓我們甚至可以為相當(dāng)噪雜的序列快速構(gòu)建異常檢測(cè)系統(tǒng),而無(wú)需花費(fèi)過(guò)多時(shí)間和金錢(qián)準(zhǔn)備數(shù)據(jù)和訓(xùn)練模型。

plt.figure(figsize=(25, 5))

plt.plot(model.PredictedDeviation)

plt.grid(True)

plt.axis('tight')

plt.title("Brutlag's predicted deviation");

遇到異常時(shí)偏差會(huì)增加

我們將在第二個(gè)序列上應(yīng)用相同的算法,我們知道,第二個(gè)序列具有趨勢(shì)和每月季節(jié)性。

data = currency.GEMS_GEMS_SPENT[:-50]

slen = 30

x = [0, 0, 0]

opt = minimize(timeseriesCVscore, x0=x,

args=(data, mean_absolute_percentage_error, slen),

method="TNC", bounds = ((0, 1), (0, 1), (0, 1))

)

alpha_final, beta_final, gamma_final = opt.x

model = HoltWinters(data, slen = slen,

alpha = alpha_final,

beta = beta_final,

gamma = gamma_final,

n_preds = 100, scaling_factor = 3)

model.triple_exponential_smoothing()

看起來(lái)很不錯(cuò),模型捕捉了向上的趨勢(shì)和季節(jié)性尖峰,總體而言很好地?cái)M合了數(shù)據(jù)。

也捕獲了一些異常

偏差隨著預(yù)測(cè)周期的推進(jìn)而上升

計(jì)量經(jīng)濟(jì)學(xué)方法

平穩(wěn)性

在開(kāi)始建模之前,我們需要提一下時(shí)序的一個(gè)重要性質(zhì):平穩(wěn)性(stationarity)。

如果過(guò)程是平穩(wěn)的,那么它的統(tǒng)計(jì)性質(zhì)不隨時(shí)間而變,也就是均值和方差不隨時(shí)間改變(方差的恒定性也稱為同方差性),同時(shí)協(xié)方差函數(shù)也不取決于時(shí)間(應(yīng)該只取決于觀測(cè)之間的距離)。Sean Abu的博客提供了一些可視化的圖片:

右邊的紅色曲線不平穩(wěn),因?yàn)榫惦S著時(shí)間增加:

這一次,右邊的紅色曲線在方差方面的運(yùn)氣不好:

最后,第i項(xiàng)和第(i+m)項(xiàng)的協(xié)方差函數(shù)不應(yīng)該是時(shí)間的函數(shù)。隨著時(shí)間推移,右邊的紅色曲線更緊了。因此,協(xié)方差不是常量。

為什么平穩(wěn)性如此重要?我們假定未來(lái)的統(tǒng)計(jì)性質(zhì)不會(huì)和現(xiàn)在觀測(cè)到的不同,在平穩(wěn)序列上做出預(yù)測(cè)很容易。大多數(shù)時(shí)序模型多多少少建模和預(yù)測(cè)這些性質(zhì)(例如均值和方差),這就是如果原序列不平穩(wěn),預(yù)測(cè)會(huì)出錯(cuò)的原因。不幸的是,我們?cè)诮炭茣?shū)以外的地方見(jiàn)到的大多數(shù)時(shí)序都是不平穩(wěn)的。不過(guò),我們可以(并且應(yīng)該)改變這一點(diǎn)。

知己知彼,百戰(zhàn)不殆。為了對(duì)抗不平穩(wěn)性,我們首先需要檢測(cè)它。我們現(xiàn)在將查看下白噪聲和隨機(jī)游走,并且了解下如何免費(fèi)從白噪聲轉(zhuǎn)到隨機(jī)游走,無(wú)需注冊(cè)和接受驗(yàn)證短信。

白噪聲圖形:

white_noise = np.random.normal(size=1000)

with plt.style.context('bmh'):

plt.figure(figsize=(15, 5))

plt.plot(white_noise)

這一通過(guò)標(biāo)準(zhǔn)正態(tài)分布生成的過(guò)程是平穩(wěn)的,以0為中心振蕩,偏差為1. 現(xiàn)在我們將基于這一過(guò)程生成一個(gè)新過(guò)程,其中相鄰值之間的關(guān)系為:xt= ρxt-1+ et

def plotProcess(n_samples=1000, rho=0):

x = w = np.random.normal(size=n_samples)

for t in range(n_samples):

x[t] = rho * x[t-1] + w[t]

with plt.style.context('bmh'):

plt.figure(figsize=(10, 3))

plt.plot(x)

plt.title("Rho {} Dickey-Fuller p-value: {}".format(rho, round(sm.tsa.stattools.adfuller(x)[1], 3)))

for rho in [0, 0.6, 0.9, 1]:

plotProcess(rho=rho)

第一張圖上你可以看到之前的平穩(wěn)的白噪聲。第二張圖的ρ值增加到0.6,導(dǎo)致周期更寬了,但總體上還是平穩(wěn)的。第三張圖更偏離均值0,但仍以其為中心振蕩。最后,ρ值為1時(shí)我們得到了隨機(jī)游走過(guò)程——不平穩(wěn)的時(shí)序。

這是因?yàn)檫_(dá)到閾值后,時(shí)序xt= ρxt-1+ et不再回歸其均值。如果我們從等式的兩邊減去xt-1,我們將得到xt- xt-1= (ρ-1)xt-1+ et,其中等式左邊的表達(dá)式稱為一階差分(first difference)。如果ρ = 1,那么一階差分將是平穩(wěn)的白噪聲et。這一事實(shí)是時(shí)序平穩(wěn)性的迪基-福勒檢驗(yàn)(Dickey-Fuller test)的主要思想(檢驗(yàn)是否存在單位根)。如果非平穩(wěn)序列可以通過(guò)一階差分得到平穩(wěn)序列,那么這樣的序列稱為一階單整(integrated of order 1)序列。需要指出的是,一階差分并不總是足以得到平穩(wěn)序列,因?yàn)檫^(guò)程可能是d階單整且d > 1(具有多個(gè)單位根),在這樣的情形下,需要使用增廣迪基-福勒檢驗(yàn)(augmented Dickey-Fuller test)。

我們可以使用不同方法對(duì)抗不平穩(wěn)性——多階差分,移除趨勢(shì)和季節(jié)性,平滑,以及Box-Cox變換或?qū)?shù)變換。

創(chuàng)建SARIMA擺脫不平穩(wěn)性

現(xiàn)在,讓我們歷經(jīng)使序列平穩(wěn)的多層地獄,創(chuàng)建一個(gè)ARIMA模型。

def tsplot(y, lags=None, figsize=(12, 7), style='bmh'):

"""

繪制時(shí)序及其ACF(自相關(guān)性函數(shù))、PACF(偏自相關(guān)性函數(shù)),計(jì)算迪基-福勒檢驗(yàn)

y - 時(shí)序

lags - ACF、PACF計(jì)算所用的時(shí)差

"""

ifnot isinstance(y, pd.Series):

y = pd.Series(y)

with plt.style.context(style):

fig = plt.figure(figsize=figsize)

layout = (2, 2)

ts_ax = plt.subplot2grid(layout, (0, 0), colspan=2)

acf_ax = plt.subplot2grid(layout, (1, 0))

pacf_ax = plt.subplot2grid(layout, (1, 1))

y.plot(ax=ts_ax)

p_value = sm.tsa.stattools.adfuller(y)[1]

ts_ax.set_title('Time Series Analysis Plots Dickey-Fuller: p={0:.5f}'.format(p_value))

smt.graphics.plot_acf(y, lags=lags, ax=acf_ax)

smt.graphics.plot_pacf(y, lags=lags, ax=pacf_ax)

plt.tight_layout()

tsplot(ads.Ads, lags=60)

上:時(shí)序;左下:自相關(guān)性;右下:偏自相關(guān)性

出乎意料,初始序列是平穩(wěn)的,迪基-福勒檢驗(yàn)拒絕了單位根存在的零假設(shè)。實(shí)際上,從上面的圖形本身就可以看出這一點(diǎn)——沒(méi)有可見(jiàn)的趨勢(shì),所以均值是恒定的,整個(gè)序列的方差也相對(duì)比較穩(wěn)定。在建模之前我們只需處理季節(jié)性。為此讓我們采用“季節(jié)差分”,也就是對(duì)序列進(jìn)行簡(jiǎn)單的減法操作,時(shí)差等于季節(jié)周期。

ads_diff = ads.Ads - ads.Ads.shift(24)

tsplot(ads_diff[24:], lags=60)

好多了,可見(jiàn)的季節(jié)性消失了,然而自相關(guān)函數(shù)仍然有過(guò)多顯著的時(shí)差。為了移除它們,我們將取一階差分:從序列中減去自身(時(shí)差為1)

ads_diff = ads_diff - ads_diff.shift(1)

tsplot(ads_diff[24+1:], lags=60)

完美!我們的序列看上去是難以用筆墨形容的完美!在零周圍振蕩,迪基-福勒檢驗(yàn)表明它是平穩(wěn)的,ACF中顯著的尖峰不見(jiàn)了。我們終于可以開(kāi)始建模了!

ARIMA系速成教程

我們將逐字母講解SARIMA(p,d,q)(P,D,Q,s),季節(jié)自回歸移動(dòng)平均模型(Seasonal Autoregression Moving Average model):

AR(p)—— 自回歸模型,也就是在時(shí)序自身之上回歸。基本假設(shè)是當(dāng)前序列值取決于某個(gè)(或若干個(gè))時(shí)差前的值。模型中的最大時(shí)差記為p。通過(guò)PACF圖決定初始p值——找到最大的顯著時(shí)差,之后大多數(shù)其他時(shí)差變得不顯著。

MA(q)—— 移動(dòng)平均模型。這里不討論它的細(xì)節(jié),總之它基于以下假設(shè)建模時(shí)序的誤差,當(dāng)前誤差取決于某個(gè)時(shí)差前的值(記為q)。基于和自回歸模型類似的邏輯,可以通過(guò)ACF圖找出初始值。

讓我們把這4個(gè)字母組合起來(lái):

AR(p) + MA(q) = ARMA(p,q)

這里我們得到了自回歸移動(dòng)平均模型!如果序列是平穩(wěn)的,我們可以通過(guò)這4個(gè)字母逼近這一序列。

I(d)—— d階單整。它不過(guò)是使序列平穩(wěn)所需的非季節(jié)性差分?jǐn)?shù)。在我們的例子中,它等于1,因?yàn)槲覀兪褂靡浑A差分。

加上這一字母后我們得到了ARIMA模型,可以通過(guò)非季節(jié)性差分處理非平穩(wěn)數(shù)據(jù)。

S(s)—— 這個(gè)字母代表季節(jié)性,s為序列的季節(jié)周期長(zhǎng)度。

加上最后一個(gè)字母S后,我們發(fā)現(xiàn)這最后一個(gè)字母除了s之外,還附帶了三個(gè)額外參數(shù)——(P,D,Q)。

P—— 模型的季節(jié)分量的自回歸階數(shù),同樣可以從PACF得到,但是這次需要查看季節(jié)周期長(zhǎng)度的倍數(shù)的顯著時(shí)差的數(shù)量。例如,如果周期長(zhǎng)度等于24,查看PACF發(fā)現(xiàn)第24個(gè)時(shí)差和第48個(gè)時(shí)差顯著,那么初始P值應(yīng)當(dāng)是2.

Q—— 移動(dòng)平均模型的季節(jié)分量的階數(shù),初始值的確定和P同理,只不過(guò)使用ACF圖形。

D—— 季節(jié)性單整階數(shù)。等于1或0,分別表示是否應(yīng)用季節(jié)差分。

了解了如何設(shè)置初始參數(shù)后,讓我們回過(guò)頭去重新看下最終的圖形:

p最有可能是4,因?yàn)檫@是PACF上最后一個(gè)顯著的時(shí)差,之后大多數(shù)時(shí)差變得不顯著。

d等于1,因?yàn)槲覀儾捎玫氖且浑A差分。

q大概也等于4,這可以從ACF上看出來(lái)。

P可能等于2,因?yàn)镻ACF上第24個(gè)時(shí)差和第48個(gè)時(shí)差某種程度上比較顯著。

D等于1,我們應(yīng)用了季節(jié)差分。

Q大概是1,ACF上第24個(gè)時(shí)差是顯著的,而第48個(gè)時(shí)差不顯著。

現(xiàn)在我們打算測(cè)試不同的參數(shù)組合,看看哪個(gè)是最好的:

# 設(shè)定初始值和初始范圍

ps = range(2, 5)

d=1

qs = range(2, 5)

Ps = range(0, 3)

D=1

Qs = range(0, 2)

s = 24

# 創(chuàng)建參數(shù)所有可能組合的列表

parameters = product(ps, qs, Ps, Qs)

parameters_list = list(parameters)

len(parameters_list)的結(jié)果是54,也就是說(shuō),共有54種組合。

def optimizeSARIMA(parameters_list, d, D, s):

"""

返回參數(shù)和相應(yīng)的AIC的dataframe

parameters_list - (p, q, P, Q)元組列表

d - ARIMA模型的單整階

D - 季節(jié)性單整階

s - 季節(jié)長(zhǎng)度

"""

results = []

best_aic = float("inf")

for param in tqdm_notebook(parameters_list):

# 由于有些組合不能收斂,所以需要使用try-except

try:

model=sm.tsa.statespace.SARIMAX(ads.Ads, order=(param[0], d, param[1]),

seasonal_order=(param[3], D, param[3], s)).fit(disp=-1)

except:

continue

aic = model.aic

# 保存最佳模型、AIC、參數(shù)

if aic < best_aic:

best_model = model

best_aic = aic

best_param = param

results.append([param, model.aic])

result_table = pd.DataFrame(results)

result_table.columns = ['parameters', 'aic']

# 遞增排序,AIC越低越好

result_table = result_table.sort_values(by='aic', ascending=True).reset_index(drop=True)

return result_table

result_table = optimizeSARIMA(parameters_list, d, D, s)

# 設(shè)定參數(shù)為給出最低AIC的參數(shù)組合

p, q, P, Q = result_table.parameters[0]

best_model=sm.tsa.statespace.SARIMAX(ads.Ads, order=(p, d, q),

seasonal_order=(P, D, Q, s)).fit(disp=-1)

print(best_model.summary())

讓我們查看下這一模型的殘余分量(residual):

tsplot(best_model.resid[24+1:], lags=60)

很明顯,殘余是平穩(wěn)的,沒(méi)有明顯的自相關(guān)性。

讓我們使用這一模型進(jìn)行預(yù)測(cè):

def plotSARIMA(series, model, n_steps):

"""

繪制模型預(yù)測(cè)值與實(shí)際數(shù)據(jù)對(duì)比圖

series - 時(shí)序數(shù)據(jù)集

model - SARIMA模型

n_steps - 預(yù)測(cè)未來(lái)的步數(shù)

"""

data = series.copy()

data.columns = ['actual']

data['arima_model'] = model.fittedvalues

# 平移s+d步,因?yàn)椴罘值木壒剩懊娴囊恍?shù)據(jù)沒(méi)有被模型觀測(cè)到

data['arima_model'][:s+d] = np.NaN

forecast = model.predict(start = data.shape[0], end = data.shape[0]+n_steps)

forecast = data.arima_model.append(forecast)

# 計(jì)算誤差,同樣平移s+d步

error = mean_absolute_percentage_error(data['actual'][s+d:], data['arima_model'][s+d:])

plt.figure(figsize=(15, 7))

plt.title("Mean Absolute Percentage Error: {0:.2f}%".format(error))

plt.plot(forecast, color='r', label="model")

plt.axvspan(data.index[-1], forecast.index[-1], alpha=0.5, color='lightgrey')

plt.plot(data.actual, label="actual")

plt.legend()

plt.grid(True);

plotSARIMA(ads, best_model, 50)

最終我們得到了相當(dāng)不錯(cuò)的預(yù)測(cè),模型的平均誤差率是4.01%,這非常非常好。但是為了達(dá)到這一精確度,在準(zhǔn)備數(shù)據(jù)、使序列平穩(wěn)化、暴力搜索參數(shù)上付出了太多。

時(shí)序數(shù)據(jù)上的線性(以及不那么線性)模型

在我的工作中,創(chuàng)建模型的指導(dǎo)原則常常是快、好、省。這意味著有些模型永遠(yuǎn)不會(huì)用于生產(chǎn)環(huán)境,因?yàn)樗鼈冃枰^(guò)長(zhǎng)的時(shí)間準(zhǔn)備數(shù)據(jù)(比如SARIMA),或者需要頻繁地重新訓(xùn)練新數(shù)據(jù)(比如SARIMA),或者很難調(diào)整(比如SARIMA)。相反,我經(jīng)常使用輕松得多的方法,從現(xiàn)有時(shí)序中選取一些特征,然后創(chuàng)建一個(gè)簡(jiǎn)單的線性回歸或隨機(jī)森林模型。又快又省。

也許這個(gè)方法沒(méi)有充分的理論支撐,打破了一些假定(比如,高斯-馬爾可夫定理,特別是誤差不相關(guān)的部分),但在實(shí)踐中,這很有用,在機(jī)器學(xué)習(xí)競(jìng)賽中也相當(dāng)常用。

特征提取

很好,模型需要特征,而我們所有的不過(guò)是1維時(shí)序。我們可以提取什么特征?

首先當(dāng)然是時(shí)差。

窗口統(tǒng)計(jì)量:

窗口內(nèi)序列的最大/小值

窗口的平均數(shù)/中位數(shù)

窗口的方差

等等

日期和時(shí)間特征

每小時(shí)的第幾分鐘,每天的第幾小時(shí),每周的第幾天,你懂的

這一天是節(jié)假日嗎?也許有什么特別的事情發(fā)生了?這可以作為布爾值特征

目標(biāo)編碼

其他模型的預(yù)測(cè)(不過(guò)如此預(yù)測(cè)的話會(huì)損失速度)

讓我們運(yùn)行一些模型,看看我們可以從廣告序列中提取什么

時(shí)序的時(shí)差

將序列往回移動(dòng)n步,我們能得到一個(gè)特征,其中時(shí)序的當(dāng)前值和其t-n時(shí)刻的值對(duì)齊。如果我們移動(dòng)1時(shí)差,并訓(xùn)練模型預(yù)測(cè)未來(lái),那么模型將能夠提前預(yù)測(cè)1步。增加時(shí)差,比如,增加到6,可以讓模型提前預(yù)測(cè)6步,不過(guò)它需要在觀測(cè)到數(shù)據(jù)的6步之后才能利用。如果在這期間序列發(fā)生了根本性的變動(dòng),那么模型無(wú)法捕捉這一變動(dòng),會(huì)返回誤差很大的預(yù)測(cè)。因此,時(shí)差的選取需要平衡預(yù)測(cè)的質(zhì)量和時(shí)長(zhǎng)。

data = pd.DataFrame(ads.Ads.copy())

data.columns = ["y"]

for i in range(6, 25):

data["lag_{}".format(i)] = data.y.shift(i)

from sklearn.linear_model importLinearRegression

from sklearn.model_selection import cross_val_score

# 5折交叉驗(yàn)證

tscv = TimeSeriesSplit(n_splits=5)

def timeseries_train_test_split(X, y, test_size):

test_index = int(len(X)*(1-test_size))

X_train = X.iloc[:test_index]

y_train = y.iloc[:test_index]

X_test = X.iloc[test_index:]

y_test = y.iloc[test_index:]

return X_train, X_test, y_train, y_test

def plotModelResults(model, X_train=X_train, X_test=X_test, plot_intervals=False, plot_anomalies=False):

prediction = model.predict(X_test)

plt.figure(figsize=(15, 7))

plt.plot(prediction, "g", label="prediction", linewidth=2.0)

plt.plot(y_test.values, label="actual", linewidth=2.0)

if plot_intervals:

cv = cross_val_score(model, X_train, y_train,

cv=tscv,

scoring="neg_mean_absolute_error")

mae = cv.mean() * (-1)

deviation = cv.std()

scale = 1.96

lower = prediction - (mae + scale * deviation)

upper = prediction + (mae + scale * deviation)

plt.plot(lower, "r--", label="upper bond / lower bond", alpha=0.5)

plt.plot(upper, "r--", alpha=0.5)

if plot_anomalies:

anomalies = np.array([np.NaN]*len(y_test))

anomalies[y_test

anomalies[y_test>upper] = y_test[y_test>upper]

plt.plot(anomalies, "o", markersize=10, label = "Anomalies")

error = mean_absolute_percentage_error(prediction, y_test)

plt.title("Mean absolute percentage error {0:.2f}%".format(error))

plt.legend(loc="best")

plt.tight_layout()

plt.grid(True);

def plotCoefficients(model):

"""

繪制模型排序后的系數(shù)

"""

coefs = pd.DataFrame(model.coef_, X_train.columns)

coefs.columns = ["coef"]

coefs["abs"] = coefs.coef.apply(np.abs)

coefs = coefs.sort_values(by="abs", ascending=False).drop(["abs"], axis=1)

plt.figure(figsize=(15, 7))

coefs.coef.plot(kind='bar')

plt.grid(True, axis='y')

plt.hlines(y=0, xmin=0, xmax=len(coefs), linestyles='dashed');

y = data.dropna().y

X = data.dropna().drop(['y'], axis=1)

# 保留30%數(shù)據(jù)用于測(cè)試

X_train, X_test, y_train, y_test = timeseries_train_test_split(X, y, test_size=0.3)

# 機(jī)器學(xué)習(xí)

lr = LinearRegression()

lr.fit(X_train, y_train)

plotModelResults(lr, plot_intervals=True)

plotCoefficients(lr)

模型預(yù)測(cè)值:綠線為預(yù)測(cè)值,藍(lán)線為實(shí)際值

模型系數(shù)

好吧,簡(jiǎn)單的時(shí)差和線性回歸給出的預(yù)測(cè)質(zhì)量和SARIMA差得不遠(yuǎn)。有大量不必要的特征,不過(guò)我們將在之后進(jìn)行特征選擇。現(xiàn)在讓我們繼續(xù)增加特征!

我們將在數(shù)據(jù)集中加入小時(shí)、星期幾、是否周末三個(gè)特征。為此我們需要轉(zhuǎn)換當(dāng)前dataframe的索引為datetime格式,并從中提取hour和weekday。

data.index = data.index.to_datetime()

data["hour"] = data.index.hour

data["weekday"] = data.index.weekday

data['is_weekend'] = data.weekday.isin([5,6])*1

可視化所得特征:

plt.figure(figsize=(16, 5))

plt.title("Encoded features")

data.hour.plot()

data.weekday.plot()

data.is_weekend.plot()

plt.grid(True);

藍(lán)線:小時(shí);綠線:星期幾;紅色:是否周末

由于現(xiàn)有的變量尺度不同——時(shí)差的長(zhǎng)度數(shù)千,類別變量的尺度數(shù)十——將它們轉(zhuǎn)換為同一尺度再合理不過(guò),這樣也便于探索特征重要性,以及之后的正則化。

from sklearn.preprocessing importStandardScaler

scaler = StandardScaler()

y = data.dropna().y

X = data.dropna().drop(['y'], axis=1)

X_train, X_test, y_train, y_test = timeseries_train_test_split(X, y, test_size=0.3)

X_train_scaled = scaler.fit_transform(X_train)

X_test_scaled = scaler.transform(X_test)

lr = LinearRegression()

lr.fit(X_train_scaled, y_train)

plotModelResults(lr, X_train=X_train_scaled, X_test=X_test_scaled, plot_intervals=True)

plotCoefficients(lr)

測(cè)試誤差略有下降。從上面的系數(shù)圖像我們可以得出結(jié)論,weekday和is_weekend是非常有用的特征。

目標(biāo)編碼

我想介紹另一種類別變量編碼的變體——基于均值。如果不想讓成噸的獨(dú)熱編碼使模型暴漲,同時(shí)導(dǎo)致距離信息損失,同時(shí)又因?yàn)椤?點(diǎn) < 23點(diǎn)”之類的沖突無(wú)法使用實(shí)數(shù)值,那么我們可以用相對(duì)易于解釋的值編碼變量。很自然的一個(gè)想法是使用均值編碼目標(biāo)變量。在我們的例子中,星期幾和一天的第幾小時(shí)可以通過(guò)那一天或那一小時(shí)瀏覽的廣告平均數(shù)編碼。非常重要的是,確保均值是在訓(xùn)練集上計(jì)算的(或者交叉驗(yàn)證當(dāng)前的折),避免模型偷窺未來(lái)。

def code_mean(data, cat_feature, real_feature):

"""

返回一個(gè)字典,鍵為cat_feature的類別,

值為real_feature的均值。

"""

return dict(data.groupby(cat_feature)[real_feature].mean())

讓我們看下小時(shí)平均:

average_hour = code_mean(data, 'hour', "y")

plt.figure(figsize=(7, 5))

plt.title("Hour averages")

pd.DataFrame.from_dict(average_hour, orient='index')[0].plot()

plt.grid(True);

最后,讓我們定義一個(gè)函數(shù)完成所有的轉(zhuǎn)換:

def prepareData(series, lag_start, lag_end, test_size, target_encoding=False):

data = pd.DataFrame(series.copy())

data.columns = ["y"]

for i in range(lag_start, lag_end):

data["lag_{}".format(i)] = data.y.shift(i)

data.index = data.index.to_datetime()

data["hour"] = data.index.hour

data["weekday"] = data.index.weekday

data['is_weekend'] = data.weekday.isin([5,6])*1

if target_encoding:

test_index = int(len(data.dropna())*(1-test_size))

data['weekday_average'] = list(map(

code_mean(data[:test_index], 'weekday', "y").get, data.weekday))

data["hour_average"] = list(map(

code_mean(data[:test_index], 'hour', "y").get, data.hour))

data.drop(["hour", "weekday"], axis=1, inplace=True)

y = data.dropna().y

X = data.dropna().drop(['y'], axis=1)

X_train, X_test, y_train, y_test =

timeseries_train_test_split(X, y, test_size=test_size)

return X_train, X_test, y_train, y_test

X_train, X_test, y_train, y_test =

prepareData(ads.Ads, lag_start=6, lag_end=25, test_size=0.3, target_encoding=True)

X_train_scaled = scaler.fit_transform(X_train)

X_test_scaled = scaler.transform(X_test)

lr = LinearRegression()

lr.fit(X_train_scaled, y_train)

plotModelResults(lr, X_train=X_train_scaled, X_test=X_test_scaled,

plot_intervals=True, plot_anomalies=True)

plotCoefficients(lr)

這里出現(xiàn)了過(guò)擬合!Hour_average變量在訓(xùn)練數(shù)據(jù)集上表現(xiàn)如此優(yōu)異,模型決定集中全力在這個(gè)變量上——這導(dǎo)致預(yù)測(cè)質(zhì)量下降。處理這一問(wèn)題有多種方法,比如,我們可以不在整個(gè)訓(xùn)練集上計(jì)算目標(biāo)編碼,而是在某個(gè)窗口上計(jì)算,從最后觀測(cè)到的窗口得到的編碼大概能夠更好地描述序列的當(dāng)前狀態(tài)。或者我們可以直接手工移除這一特征,反正我們已經(jīng)確定它只會(huì)帶來(lái)壞處。

X_train, X_test, y_train, y_test =

prepareData(ads.Ads, lag_start=6, lag_end=25, test_size=0.3, target_encoding=False)

X_train_scaled = scaler.fit_transform(X_train)

X_test_scaled = scaler.transform(X_test)

正則化和特征選取

正如我們已經(jīng)知道的那樣,并不是所有的特征都一樣健康,有些可能導(dǎo)致過(guò)擬合。除了手工檢查外我們還可以應(yīng)用正則化。最流行的兩個(gè)帶正則化的回歸模型是嶺(Ridge)回歸和Lasso回歸。它們都在損失函數(shù)上施加了某種限制。

在嶺回歸的情形下,限制是系數(shù)的平方和,乘以正則化系數(shù)。也就是說(shuō),特征系數(shù)越大,損失越大,因此優(yōu)化模型的同時(shí)將盡可能地保持系數(shù)在較低水平。

因?yàn)橄拗剖窍禂?shù)的平方和,所以這一正則化方法稱為L(zhǎng)2。它將導(dǎo)致更高的偏差和更低的方差,所以模型的概括性會(huì)更好(至少這是我們希望發(fā)生的)。

第二種模型Lasso回歸,在損失函數(shù)中加上的不是平方和,而是系數(shù)絕對(duì)值之和。因此在優(yōu)化過(guò)程中,不重要的特征的系數(shù)將變?yōu)榱悖訪asso回歸可以實(shí)現(xiàn)自動(dòng)特征選擇。這類正則化稱為L(zhǎng)1。

首先,確認(rèn)下我們有特征可以移除,也就是說(shuō),確實(shí)有高度相關(guān)的特征:

plt.figure(figsize=(10, 8))

sns.heatmap(X_train.corr());

比某些現(xiàn)代藝術(shù)要漂亮

from sklearn.linear_model importLassoCV, RidgeCV

ridge = RidgeCV(cv=tscv)

ridge.fit(X_train_scaled, y_train)

plotModelResults(ridge,

X_train=X_train_scaled,

X_test=X_test_scaled,

plot_intervals=True, plot_anomalies=True)

plotCoefficients(ridge)

我們可以很清楚地看到,隨著特征在模型中的重要性的降低,系數(shù)越來(lái)越接近零(不過(guò)從未達(dá)到零):

lasso = LassoCV(cv=tscv)

lasso.fit(X_train_scaled, y_train)

plotModelResults(lasso,

X_train=X_train_scaled,

X_test=X_test_scaled,

plot_intervals=True, plot_anomalies=True)

plotCoefficients(lasso)

Lasso回k看起來(lái)更保守一點(diǎn),沒(méi)有將第23時(shí)差作為最重要特征,同時(shí)完全移除了5項(xiàng)特征,這提升了預(yù)測(cè)質(zhì)量。

XGBoost

為什么不試試XGBoost?

from xgboost importXGBRegressor

xgb = XGBRegressor()

xgb.fit(X_train_scaled, y_train)

plotModelResults(xgb,

X_train=X_train_scaled,

X_test=X_test_scaled,

plot_intervals=True, plot_anomalies=True)

我們的贏家出現(xiàn)了!在我們目前為止嘗試過(guò)的模型中,XGBoost在測(cè)試集上的誤差是最小的。

不過(guò)這一勝利帶有欺騙性,剛到手時(shí)序數(shù)據(jù),馬上嘗試XGBoost也許不是什么明智的選擇。一般而言,和線性模型相比,基于樹(shù)的模型難以應(yīng)付數(shù)據(jù)中的趨勢(shì),所以你首先需要從序列中去除趨勢(shì),或者使用一些特殊技巧。理想情況下,平穩(wěn)化序列,接著使用XGBoost,例如,你可以使用一個(gè)線性模型單獨(dú)預(yù)測(cè)趨勢(shì),然后將其加入XGBoost的預(yù)測(cè)以得到最終預(yù)測(cè)。

結(jié)語(yǔ)

我們熟悉了不同的時(shí)序分析和預(yù)測(cè)方法。很不幸,或者,很幸運(yùn),解決這類問(wèn)題沒(méi)有銀彈。上世紀(jì)60年代研發(fā)的方法(有些甚至在19世紀(jì)就提出了)和LSTM、RNN(本文沒(méi)有介紹)一樣流行。這部分是因?yàn)闀r(shí)序預(yù)測(cè)任務(wù)和任何其他數(shù)據(jù)預(yù)測(cè)任務(wù)一樣,在許多方面都需要?jiǎng)?chuàng)造性和研究。盡管有眾多形式化的質(zhì)量測(cè)度和參數(shù)估計(jì)方法,我們常常需要為每個(gè)序列搜尋并嘗試一些不同的東西。最后,平衡質(zhì)量和成本很重要。之前提到的SARIMA模型是一個(gè)很好的例子,經(jīng)過(guò)調(diào)節(jié)之后,它常常能生成出色的結(jié)果,但這需要許多小時(shí)、許多復(fù)雜技巧來(lái)處理數(shù)據(jù),相反,10分鐘之內(nèi)就可以創(chuàng)建好的簡(jiǎn)單線性回歸模型卻能取得相當(dāng)?shù)慕Y(jié)果。

聲明:本文內(nèi)容及配圖由入駐作者撰寫(xiě)或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場(chǎng)。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問(wèn)題,請(qǐng)聯(lián)系本站處理。 舉報(bào)投訴
  • 函數(shù)
    +關(guān)注

    關(guān)注

    3

    文章

    4369

    瀏覽量

    64187
  • 時(shí)序
    +關(guān)注

    關(guān)注

    5

    文章

    397

    瀏覽量

    37782
  • python
    +關(guān)注

    關(guān)注

    56

    文章

    4825

    瀏覽量

    86175

原文標(biāo)題:機(jī)器學(xué)習(xí)開(kāi)放課程:九、基于Python分析真實(shí)手游時(shí)序數(shù)據(jù)

文章出處:【微信號(hào):jqr_AI,微信公眾號(hào):論智】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。

收藏 人收藏

    評(píng)論

    相關(guān)推薦
    熱點(diǎn)推薦

    時(shí)序數(shù)據(jù)庫(kù)HiTSDB的深度解析!

    深度解讀!時(shí)序數(shù)據(jù)庫(kù)HiTSDB:分布式流式聚合引擎
    發(fā)表于 07-22 13:22

    多片段時(shí)序數(shù)據(jù)建模預(yù)測(cè)實(shí)踐資料分享

    時(shí)序數(shù)據(jù)建模分析已經(jīng)有很多相關(guān)的應(yīng)用了,在這個(gè)領(lǐng)域里面LSTM網(wǎng)絡(luò)絕對(duì)是占據(jù)著非常重要的作用,自從LSTM網(wǎng)絡(luò)提出以來(lái),陸陸續(xù)續(xù)又出現(xiàn)了很多相關(guān)的變種網(wǎng)絡(luò),傳統(tǒng)的時(shí)序建模工作主要是基于
    發(fā)表于 06-30 07:52

    關(guān)于時(shí)序數(shù)據(jù)庫(kù)的內(nèi)容

    簡(jiǎn)介: 這是一篇無(wú)法一口氣讀完的、文字過(guò)萬(wàn)[正文字?jǐn)?shù)14390]的長(zhǎng)文,這是一個(gè)無(wú)法中途不上廁所就看完的、關(guān)于時(shí)序數(shù)據(jù)庫(kù)的視頻[時(shí)長(zhǎng)111分鐘]分享的文字整理..大家好,很開(kāi)心能夠和大家一起交流時(shí)序數(shù)據(jù)
    發(fā)表于 07-12 08:00

    什么是時(shí)序數(shù)據(jù)庫(kù)?

    本文根據(jù)演講視頻以及PPT整理而成。本文將主要圍繞以下四個(gè)方面進(jìn)行分享:時(shí)序數(shù)據(jù)時(shí)序數(shù)據(jù)庫(kù)時(shí)序數(shù)據(jù)庫(kù)的演變時(shí)序數(shù)據(jù)庫(kù)對(duì)比總結(jié)一、
    發(fā)表于 07-12 08:35

    同步時(shí)序數(shù)字電路的分析

    同步時(shí)序數(shù)字電路的分析二進(jìn)制同步計(jì)數(shù)器 分析步驟: 1.確定電路是否是同步時(shí)序數(shù)字電路 2.確定觸發(fā)器的驅(qū)動(dòng)方程 3.做出狀態(tài)轉(zhuǎn)換表 4.做出分析
    發(fā)表于 10-20 10:10 ?30次下載
    同步<b class='flag-5'>時(shí)序數(shù)</b>字電路的<b class='flag-5'>分析</b>

    網(wǎng)絡(luò)流量時(shí)序數(shù)據(jù)可視分析

    網(wǎng)絡(luò)安全可視化作為一個(gè)交叉應(yīng)用研究領(lǐng)域,為傳統(tǒng)網(wǎng)絡(luò)安全數(shù)據(jù)分析方法注入了新的活力.但已有研究過(guò)于注重網(wǎng)絡(luò)安全數(shù)據(jù)的可視表達(dá),而忽視了對(duì)分析流程的支持.抽象了網(wǎng)絡(luò)安全
    發(fā)表于 01-14 16:32 ?0次下載

    TableStore時(shí)序數(shù)據(jù)存儲(chǔ) - 架構(gòu)篇

    數(shù)據(jù)點(diǎn)的展示,另一個(gè)最主要的目的是為了降低存儲(chǔ)成本。?分析(Analytic)分析是為了從時(shí)序數(shù)據(jù)中挖掘更多
    發(fā)表于 08-08 16:17 ?719次閱讀
    TableStore<b class='flag-5'>時(shí)序數(shù)據(jù)</b>存儲(chǔ) - 架構(gòu)篇

    時(shí)序數(shù)據(jù)庫(kù)的前世今生

    ? 時(shí)序數(shù)據(jù)庫(kù)忽然火了起來(lái)。Facebook開(kāi)源了beringei時(shí)序數(shù)據(jù)庫(kù),基于PostgreSQL打造的時(shí)序數(shù)據(jù)庫(kù)TimeScaleDB也開(kāi)源了。時(shí)序數(shù)據(jù)庫(kù)作為物聯(lián)網(wǎng)方向一個(gè)非常重
    的頭像 發(fā)表于 12-17 17:51 ?3888次閱讀

    華為時(shí)序數(shù)據(jù)庫(kù)為智慧健康養(yǎng)老行業(yè)貢獻(xiàn)應(yīng)用之道

    隨著 IoT 技術(shù)的快速發(fā)展,物聯(lián)網(wǎng)設(shè)備產(chǎn)生的數(shù)據(jù)呈爆炸式增長(zhǎng)。這些數(shù)據(jù)通常隨時(shí)間產(chǎn)生,稱之為時(shí)序數(shù)據(jù)。這樣的一種專門(mén)用于管理時(shí)序數(shù)據(jù)數(shù)據(jù)
    的頭像 發(fā)表于 11-07 15:10 ?6147次閱讀

    華為PB級(jí)時(shí)序數(shù)據(jù)庫(kù)Gauss DB,助力海量數(shù)據(jù)處理

    時(shí)序數(shù)據(jù)作為大數(shù)據(jù)、機(jī)器學(xué)習(xí)、實(shí)時(shí)預(yù)測(cè)的基礎(chǔ)數(shù)據(jù),作用更加顯著。因此,對(duì)時(shí)序數(shù)據(jù)的研究與應(yīng)用應(yīng)當(dāng)更為深入。 ??近 5 年來(lái),
    的頭像 發(fā)表于 10-15 19:15 ?1319次閱讀
    華為PB級(jí)<b class='flag-5'>時(shí)序數(shù)據(jù)</b>庫(kù)Gauss DB,助力海量<b class='flag-5'>數(shù)據(jù)處理</b>

    物聯(lián)網(wǎng)場(chǎng)景海量時(shí)序數(shù)據(jù)存儲(chǔ)與處理的關(guān)鍵技術(shù)

    時(shí)序數(shù)據(jù)是隨時(shí)間不斷產(chǎn)生的一系列數(shù)據(jù),例如持續(xù)監(jiān)控的氣象變化數(shù)據(jù)、股市交易記錄、應(yīng)用監(jiān)控數(shù)據(jù)等,通常一個(gè)時(shí)序數(shù)據(jù)點(diǎn)可以由
    發(fā)表于 12-27 11:58 ?2713次閱讀

    涂鴉推出NekoDB時(shí)序數(shù)據(jù)庫(kù),助力全球客戶實(shí)現(xiàn)低成本部署

    隨著IoT技術(shù)逐漸成熟,眾多設(shè)備產(chǎn)出的數(shù)據(jù)呈現(xiàn)指數(shù)級(jí)增長(zhǎng)。企業(yè)亟需用行之有效的方式管理海量時(shí)序數(shù)據(jù)。由此,各類時(shí)序數(shù)據(jù)庫(kù)開(kāi)始成為市場(chǎng)寵兒。與市場(chǎng)需求相悖的是,時(shí)序數(shù)據(jù)庫(kù)水平參差不齊。縱
    的頭像 發(fā)表于 07-24 10:08 ?2270次閱讀
    涂鴉推出NekoDB<b class='flag-5'>時(shí)序數(shù)據(jù)</b>庫(kù),助力全球客戶實(shí)現(xiàn)低成本部署

    Tsmoothie:使用多種平滑技術(shù)平滑化時(shí)序數(shù)據(jù)

    除,平滑后的效果如下: 這樣的時(shí)序數(shù)據(jù)是不是看起來(lái)舒服多了?此外,使用平滑后的時(shí)序數(shù)據(jù)去做聚類或預(yù)測(cè)或許有令人驚艷的效果,因?yàn)樗コ艘恍┢钪挡⒓?xì)化了數(shù)據(jù)的分布范圍。 如果我們自己開(kāi)
    的頭像 發(fā)表于 10-30 09:28 ?1792次閱讀
    Tsmoothie:使用多種平滑技術(shù)平滑化<b class='flag-5'>時(shí)序數(shù)據(jù)</b>

    時(shí)序數(shù)據(jù)庫(kù)是什么?時(shí)序數(shù)據(jù)庫(kù)的特點(diǎn)

    時(shí)序數(shù)據(jù)庫(kù)是一種在處理時(shí)間序列數(shù)據(jù)方面具有高效和專門(mén)化能力的數(shù)據(jù)庫(kù)。它主要用于存儲(chǔ)和處理時(shí)間序列
    的頭像 發(fā)表于 04-26 16:02 ?956次閱讀

    TDengine 發(fā)布時(shí)序數(shù)據(jù)分析 AI 智能體 TDgpt,核心代碼開(kāi)源

    組成部分,標(biāo)志著時(shí)序數(shù)據(jù)庫(kù)在原生集成 AI 能力方面邁出了關(guān)鍵一步。 TDgpt 是內(nèi)嵌于 TDengine 中的時(shí)序數(shù)據(jù)分析 AI 智能體,具備時(shí)序數(shù)據(jù)預(yù)測(cè)、異常檢測(cè)、數(shù)據(jù)補(bǔ)全、分類
    的頭像 發(fā)表于 03-27 10:30 ?233次閱讀
    TDengine 發(fā)布<b class='flag-5'>時(shí)序數(shù)據(jù)分析</b> AI 智能體 TDgpt,核心代碼開(kāi)源