乾貨!隱馬爾科夫模型

  • 公式推導

  • Hmmlearn

    • GaussianHMM

    • GMMHMM

    • MultinomialHMM

  • 股票走勢預測

    • 特徵準備

    • 建立模型

    • 可視化短線預測

  • 參考資料

HMM

公式推導

在 HMM 中,有兩個基本假設:

  • 齊次 Markov 假設(未來只依賴於當前): 

 

  • 觀測獨立假設: 

 

HMM 要解決三個問題:

  1. Evaluation:,Forward-Backward 算法

  2. Learning:,EM 算法(Baum-Welch)

  3. Decoding:,Vierbi 算法

    1. 預測問題:

    2. 濾波問題:

Evaluation

  

  

根據齊次 Markov 假設:

  

所以:

又由於:

於是:

  

我們看到,上面的式子中的求和符號是對所有的觀測變量求和,於是複雜度爲   。

下面,記   ,所以,  。我們看到:

  

對   :

  

利用觀測獨立假設:

  

上面利用了齊次 Markov 假設得到了一個遞推公式,這個算法叫做前向算法。

還有一種算法叫做後向算法,定義  :

  

對於這個   :

  

於是後向地得到了第一項。

Learning

爲了學習得到參數的最優值,在 MLE 中:

  

我們採用 EM 算法(在這裏也叫 Baum Welch 算法),用上標表示迭代:

  

其中,   是觀測變量,  是隱變量序列。於是:

  

 這裏利用了   無關。將 Evaluation 中的式子代入:

  

對   :

  

上面的式子中,對   求和可以將這些參數消掉:

  

上面的式子還有對   的約束  。定義 Lagrange 函數:

  

於是:

  

對上式求和:

  

所以:

  

Decoding

Decoding 問題表述爲:

  

我們需要找到一個序列,其概率最大,這個序列就是在參數空間中的一個路徑,可以採用動態規劃的思想。

定義:

  

於是:

  

這個式子就是從上一步到下一步的概率再求最大值。記這個路徑爲:

  

Hmmlearn

hmmlearn中有三種隱馬爾可夫模型:GaussianHMM、GMMHMM、MultinomialHMM。它們分別代表了觀測序列的不同分佈類型。

GaussianHMM

適合用於可見層狀態是連續類型且假設輸出概率符合Gaussian分佈的情況

class hmmlearn.hmm.GaussianHMM(n_components=1, covariance_type='diag',min_covar=0.001,
                               startprob_prior=1.0, transmat_prior=1.0, means_prior=0,      
                               means_weight=0,covars_prior=0.01,covars_weight=1,
                               algorithm='viterbi',random_state=None, n_iter=10, tol=0.01,
                               verbose=False, params='stmc', init_params='stmc')

參數:

  • n_components:整數,指定了隱藏層結點的狀態數量。

  • covariance_type:字符串,輸出概率的協方差矩陣類型。可選值:

    • 'spherical':球面協方差矩陣,即矩陣對角線上的元素都相等,且其他元素爲零

    • 'diag':對角協方差矩陣,即對角線元素可以是任意值,其他元素爲零。

    • 'full':完全矩陣,即任意元素可以是任意值。

    • 'tied':所有狀態都是用同一個普通的方差矩陣。

  • min_covar:浮點數,協方差矩陣中對角線上的最小數值,該值設置得越小模型對數據的擬合就越好,但更容易出現過度擬合。

  • startprob_prior:數組,形狀爲(n_components, )。初始狀態的先驗概率分佈。

  • transmat_prior:數組,形狀爲(n_components, n_components )。先驗的狀態轉移矩陣。

  • means_prior,means_weight:先驗隱藏層均值矩陣。

  • covars_prior、covars_weight:先驗隱藏層協方差矩陣

  • algorithm:字符串,指定了Decoder算法。可以爲 'viterbi'(維特比算法)或者'map',map比viterbi更快速但非全局最優解。

  • random_state:隨機種子,用於在Baum-Welch算法中初始化模型參數。

  • n_iter:Baum-Welch算法最大迭代次數。該值越大,訓練模型對數據的擬合度越高,但訓練耗時越長。

  • tol:指定迭代收斂閾值。該值越小(必須>=0),訓練模型對數據的擬合度越高,但訓練耗時越長。

  • verbose:是否打印Baum-Welch每次迭代的調試信息

  • params:字符串,在訓練過程中更新哪些HMM參數。可以是四個字母中的任意幾個組成的字符串。

    • 's':初始概率。

    • 't':轉移概率。

    • 'm':均值。

    • 'c':偏差。

  • init_params:字符串,在訓練開始之前使用哪些已有概率矩陣初始化模型。

    • 's':初始概率。

    • 't':轉移概率。

    • 'm':均值。

    • 'c':偏差。

屬性:

  • n_features:整數,特徵維度。

  • monitor_:ConvergenceMonitor對象,可用它檢查EM算法的收斂性。

  • transmat_:矩陣,形狀爲 (n_components, n_components),是狀態之間的轉移概率矩陣。

  • startprob_:數組,形狀爲(n_components, ),是初始狀態的概率分佈。

  • means_:一個數組,形狀爲(n_components,n_features ),每個狀態的均值參數。

  • covars_:數組,每個狀態的方差參數,其形狀取決於方差類型:

    • 'spherical':形狀爲(n_components, ) 。

    • 'diag':形狀爲(n_components,n_features ) 。

    • 'full':形狀爲(n_components, n_features, n_features) 。

    • 'tied':形狀爲(n_features,n_features ) 。

方法:

  • decode(X, lengths=None, algorithm=None):已知觀測序列X尋找最可能的狀態序列。

    • logprob:浮點數,代表產生的狀態序列的對數似然函數。

    • state_sequence:一個數組,形狀爲(n_samples, ),代表狀態序列。

    • X:array-like,形狀爲 (n_samples, n_features)。指定了觀測的樣本。

    • lengths:array-like,形狀爲 (n_sequences, )。指定了觀測樣本中,每個觀測序列的長度,其累加值必須等於n_samples 。

    • algorithm:字符串,指定解碼算法。必須是'viterbi'(維特比)或者'map'。

    • 參數:

    • 返回值:

  • fit(X, lengths=None):根據觀測序列 X,來訓練模型參數。

在訓練之前會執行初始化的步驟。如果你想避開這一步,那麼可以在構造函數中通過提供init_params關鍵字參數來避免。

  • 參數:X,lengths 參考 decode() 方法。

  • 返回值:self對象。

  • predict(X, lengths=None):已知觀測序列X,尋找最可能的狀態序列。

    • 參數:X,lengths 參考 decode() 方法。

    • 返回值:數組,形狀爲(n_samples, ),代表狀態序列。

  • predict_proba(X, lengths=None):計算每個狀態的後驗概率。

    • 參數:X,lengths 參考 decode() 方法。

    • 返回值:數組,代表每個狀態的後驗概率。

  • sample(n_samples=1, random_state=None):從當前模型中生成隨機樣本。

    • X:觀測序列,長度爲n_samples 。

    • state_sequence:狀態序列,長度爲n_samples 。

    • n_samples:生成樣本的數量。

    • random_state:指定隨機數。如果爲None,則使用構造函數中的random_state。

    • 參數:

    • 返回值:

  • score(X, lengths=None):計算預測結果的對數似然函數。

    • 參數:X,lengths 參考 decode() 方法。

    • 返回值:預測結果的對數似然函數。

GMMHMM

混合高斯分佈的隱馬爾可夫模型,適合用於隱藏層狀態是連續類型且假設輸出概率符合GMM 分佈(Gaussian Mixture Model,混合高斯分佈)的情況

原型

hmmlearn.hmm.GMMHMM(n_components=1,n_mix=1,startprob_prior=1.0,transmat_prior=1.0,
                    covariance_type='diag', covars_prior=0.01, algorithm='viterbi',
                    random_state=None,n_iter=10,tol=0.01, verbose=False,
                    params='stmcw',init_params='stmcw')

大部分參數與GaussianHMM中的含義一樣,不再重複說明,不同點有如下兩個:

  • n_mix:整數,指定了混合高斯分佈中高斯分佈的數量,如果n_min等於1,則GMMHMM退化爲GaussianHMM。

  • means_prior,means_weight,covars_prior,covars_weight:這四個參數雖然名稱與GaussianHMM一致,但其維數隨着n_mix的設置而不同。

屬性:

  • n_features:整數,特徵維度。

  • monitor_:ConvergenceMonitor對象,可用它檢查EM算法的收斂性。

  • transmat_:矩陣,形狀爲 (n_components, n_components),是狀態之間的轉移概率矩陣。

  • startprob_:數組,形狀爲(n_components, ),是初始狀態的概率分佈。

  • gmms_:列表,指定了每個狀態的混合高斯分佈的分模型。

方法:參考hmmlearn.hmm.GaussianHMM 。

MultinomialHMM

多項式分佈的隱馬爾可夫模型,適合用於可見層狀態是離散類型的情況。

原型爲:

class hmmlearn.hmm.MultinomialHMM(n_components=1, startprob_prior=1.0, transmat_prior=1.0,
                                  algorithm='viterbi', random_state=None, n_iter=10,tol=0.01,
                                  verbose=False, params='ste', init_params='ste')

參數:整數,參考hmmlearn.hmm.GaussianHMM。

屬性:

  • n_features:一個整數,特徵維度。

  • monitor_:一個ConvergenceMonitor對象,可用它檢查EM算法的收斂性。

  • transmat_:一個矩陣,形狀爲 (n_components, n_components),是狀態之間的轉移概率矩陣。

  • startprob_:一個數組,形狀爲(n_components, ),是初始狀態的概率分佈。

  • emissionprob_:一個數組,形狀爲(n_components, n_features),每個狀態的發射概率。

方法:參考hmmlearn.hmm.GaussianHMM 。

# MultinomialHMM案例
import numpy as np
from hmmlearn import hmm

# 發射概率
emission_probability = np.array([
    [0.4,0.3,0.3], # 晴:打球,讀書,訪友
    [0.2,0.3,0.5], # 陰:打球,讀書,訪友
    [0.1,0.8,0.1]  # 雨:打球,讀書,訪友
])

# 轉移概率
transition_probability = np.array([
    [0.7,0.2,0.1],# 晴 陰 雨
    [0.3,0.5,0.2],# 陰
    [0.3,0.4,0.3] # 雨
])

# 定義隱藏層各狀態的初始概率 晴、陰、雨
start_probability = np.array([0.5,0.3,0.2])


# 建模
model = hmm.MultinomialHMM(n_components=3) # 3種可見層狀態
model.startprob_ = start_probability
model.transmat_ = transition_probability
model.emissionprob_ = emission_probability

# 0爲打球,1爲讀書,2爲訪友
observe_chain = np.array([0,2,1,1,1,1,1,1,2,2,2,2,2,2,2,2,1,0]).reshape(-1,1)

# 0爲晴,1爲陰,2爲雨
model.predict(observe_chain)
array([0, 0, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0])

股票走勢預測

本案例從雅虎金融網站獲取真實的歷史交易數據,訓練並分析歷史走勢,嘗試尋找出歷史走勢規律以預測未來。下面通過用HMM模型來預測走勢規律

  • HMM 時間軸:由於數據模型是日交易信息,所以本模型的時間軸以日爲單位,即每一天是一個HMM狀態結點。

  • 可見層特徵:選取數據文件中的兩個重要數據作爲可見層特徵,即收盤漲跌值、交易量。因爲這些屬性爲連續值,所以選用Gaussian模型。收盤漲跌值特徵並未在數據模型中直接體現,它要通過計算前後兩天收盤價的差值獲得

  • 隱藏層狀態:定義三種狀態,對應於漲、平、跌。

  • 預測方式:通過查看隱藏層轉換矩陣的轉換概率,根據最後一個結點的隱藏狀態預測未來一天的漲跌可能。

隱藏層的漲跌狀態只受當天及之前表示層特徵的影響,所以其本身並不能決定下一天的走勢。而要預測下一天的隱藏層狀態,需要結合轉換概率矩陣進行分析。

from datetime import datetime
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.dates import DayLocator
from hmmlearn.hmm import GaussianHMM
import warnings
warnings.filterwarnings("ignore")

%matplotlib inline

data = pd.read_csv("sample_stock.csv")
print(data.shape)
data.head()
(1258, 7)

原始數據是以日爲單位的某股票基礎交易數據,各屬性列表示:

  • 日期

  • 開盤價

  • 當日最高價

  • 當日最低價

  • 收盤價

  • 調整收盤

  • 當日交易量

# 日期格式轉換
data['Date'] = pd.to_datetime(data['Date'], format = "%d/%m/%Y", errors = 'coerce')
data.info()
data.head()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 1258 entries, 0 to 1257
Data columns (total 7 columns):
Date         1258 non-null datetime64[ns]
Open         1258 non-null float64
High         1258 non-null float64
Low          1258 non-null float64
Close        1258 non-null float64
Adj Close    1258 non-null float64
Volume       1258 non-null int64
dtypes: datetime64[ns](1), float64(5), int64(1)
memory usage: 68.9 KB

特徵準備

日期和交易量去除第一天的數據,因爲第一天會被用來計算第二天的漲跌值特徵,所以馬爾可夫鏈實際是從第二天開始訓練的。

dates = data['Date'].values[1:]
close_v = data['Close'].values
volume = data['Volume'].values[1:]

# 計算數組中前後兩個值差額
diff  = np.diff(close_v)
close_v = close_v[1:]

X = np.column_stack([diff,volume])
X.shape
(1257, 2)

建立模型

  • n_components 參數指定了使用3個隱藏層狀態

  • covariance_type定義了協方差矩陣類型爲對角線類型,即每個特徵的高斯分佈有自己的方差參數,相互之間沒有影響

  • n_iter參數定義了Baum-Welch的最大迭代次數

model =  GaussianHMM(n_components=3, covariance_type="diag",n_iter=1000)
model.fit(X)
GaussianHMM(algorithm='viterbi', covariance_type='diag', covars_prior=0.01,
            covars_weight=1, init_params='stmc', means_prior=0, means_weight=0,
            min_covar=0.001, n_components=3, n_iter=1000, params='stmc',
            random_state=None, startprob_prior=1.0, tol=0.01,
            transmat_prior=1.0, verbose=False)
print("Means and vars of each hidden state")
means_ = []
vars_ = []
for i in range(model.n_components):
    means_.append(model.means_[i][0])
    vars_.append(np.diag(model.covars_[i])[0])
    print("第{0}號隱藏狀態".format(i))
    print("mean = ", model.means_[i])
    print("var = ", np.diag(model.covars_[i]))
    print()
Means and vars of each hidden state
第0號隱藏狀態
mean =  [-6.63217241e-03  5.88893426e+04]
var =  [4.84610308e-02 4.85498280e+08]

第1號隱藏狀態
mean =  [-7.25238348e-02  4.22133974e+05]
var =  [1.10870852e+00 1.09613199e+11]

第2號隱藏狀態
mean =  [4.41582696e-02 1.42342273e+05]
var =  [1.42504830e-01 3.27804399e+09]

因爲可見層輸入採用了兩個輸入特徵(漲跌幅、成交量),所以每個結點有兩個元素,分別代表該狀態的漲跌幅均值、成交量均值。

由於系統的目標預測漲跌幅,所以這裏只關心第一個特徵的均值,有如下結論:

  • 狀態0的均值是-6.63217241e-03,約爲−0.00663,認爲該狀態是“平”。

  • 狀態1的均值是4.41582696e-02,約爲0.044(漲4分錢),得知該狀態是“漲”。

  • 狀態2的均值是-7.25238348e-02,約爲−0.072(跌7分錢),得知該狀態是“跌”。

由漲跌幅特徵的方差,可以得到的信息爲:

  • 狀態“平”的方差爲4.84610308e-02,是三個狀態中方差最小的,也就是說該狀態的預測非常可信。

  • 狀態“漲”的方差爲1.42504830e-01,可信度居中。

  • 狀態“跌”的方差爲1.10870852e+00,是三個狀態中方差最大的,即該狀態的變化範圍較大,並非很可信。

print("Transition matrix")
print(model.transmat_)
Transition matrix
[[0.82001546 0.01739021 0.16259432]
 [0.03793618 0.22931597 0.73274785]
 [0.23556112 0.04332874 0.72111014]]
  • 第一行最大的數值是0.82001546,因此“平”傾向於保持自己的狀態,即第二天仍舊爲“平”。

  • 第二行最大的數值是0.72111014,得知“漲”後第二天傾向於變爲“漲”。

  • 根據第三行狀態,“跌”後第二天仍舊傾向於“漲”。

根據以上轉換概率,可以看出該股票的長線態勢是非常看好的。


可視化短線預測

X = X[-26:]
dates = dates[-26:]
close_v = close_v[-26:]

hidden_states = model.predict(X)
hidden_states
array([0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2,
       2, 2, 2, 2])
means_
[-0.0066321724101716766, -0.07252383481726057, 0.044158269609221555]
vars_
[0.048461030777305854, 1.1087085156561962, 0.1425048299475001]
model.transmat_
array([[0.82001546, 0.01739021, 0.16259432],
       [0.03793618, 0.22931597, 0.73274785],
       [0.23556112, 0.04332874, 0.72111014]])
# 計算當前狀態後一天的預測均值
predict_means = []
predict_vars = []

for idx, hid in enumerate(range(model.n_components)):
    comp = np.argmax(model.transmat_[idx]) # 最可能的第二天狀態
    predict_means.append(means_[comp])
    predict_vars.append(vars_[comp])

print('predict_means',predict_means)
print('predict_vars',predict_vars)
predict_means [-0.0066321724101716766, 0.044158269609221555, 0.044158269609221555]
predict_vars [0.048461030777305854, 0.1425048299475001, 0.1425048299475001]
# 畫圖
fig, axs = plt.subplots(model.n_components + 1, sharex=True, sharey=True,figsize=(10,10))

for i, ax in enumerate(axs[:-1]):
    ax.set_title("{0}th hidden state".format(i))

    # Use fancy indexing to plot data in each state.
    mask = hidden_states == i
    yesterday_mask = np.concatenate(([False], mask[:-1]))
    if len(dates[mask]) <= 0:
        continue

    if predict_means[i] > 0.01:
        # 上漲預測
        ax.plot_date(dates[mask], close_v[mask], "^", c="#FF0000")
    elif predict_means[i] < -0.01:
        # 下跌預測
        ax.plot_date(dates[mask], close_v[mask], "v", c="#00FF00")
    else:
        # 平
        ax.plot_date(dates[mask], close_v[mask], "+", c="#000000")

    # locate specified days of the day
    ax.xaxis.set_minor_locator(DayLocator())

    ax.grid(True)
    ax.legend(["Mean: %0.3f\nvar: %0.3f" % (predict_means[i], predict_vars[i])],
        loc='center left',
        bbox_to_anchor=(1, 0.5))

# 打印真實走勢,用作對比
axs[-1].plot_date(dates, close_v, "-", c='#000000')
axs[-1].grid(True)
box = axs[-1].get_position()
axs[-1].set_position([box.x0, box.y0, box.width * 0.8, box.height])
ax.xaxis.set_minor_locator(DayLocator())

# 調整格式
fig.autofmt_xdate()
plt.subplots_adjust(left=None, bottom=None, right=0.75, top=None, 
                    wspace=None, hspace=0.43)

plt.show()

由圖可知,這個月中大部分隱藏狀態是“平”或者“漲”,其中“漲”的數量更多,由最下方真實走勢圖可見當月結果確實爲上漲。最後一天的結點落在了“2th hidden state”中,其預測狀態爲上漲(均值爲0.044,方差爲0.143)。

參考資料

  • 從機器學習到深度學習

  • hmmlearn文檔

  • 統計學習方法

END
最後說個題外話,相信大家都知道視頻號了,隨着灰度範圍擴大,越來越多的小夥伴都開通了視頻號。小詹也開通了一個視頻號,會分享互聯網那些事、讀書心得與副業經驗,歡迎掃碼關注,和小詹一起向上生長!「沒有開通發佈權限的儘量多互動,提升活躍度可以更快開通哦」

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章