马尔可夫链


Ⅰ. 背景 –> Ⅱ. 原理 –> Ⅲ. 示例 –> Ⅳ. Python 代码


Ⅰ. 背景

“The future is independent of the past given the present” — Andrey Andreyevich Markov.

马尔可夫链(Markov Chain)是机器学习和人工智能的基石. 马尔可夫链的核心思想是,过去所有的信息都蕴含在当下,未来只基于此时此刻而和过去无关. 虽然在哲学角度,这种极端的说法不具有辩证性,但马尔可夫链在很多时间序列模型中得到广泛的应用.


Ⅱ. 原理

定义序列状态为 X^{(i)},\ i=0,1,2,\ldots,n. 例如,第 0 天是晴天,第 1 天是雨天,第 2 天是雪天.

满足马尔可夫假设:

\Pr(X^{(n+1)}\mid X^{(n)},X^{(n-1)},\ldots,X^{(0)})=\Pr(X^{(n+1)}\mid X^{(n)})

第 n+1 次出现某状态的概率独立于先前所有状态,等价于在第 n 次状态发生后第 n+1 次的概率. 这种现象称之为无后效性、无记忆性.

统计以前所有相同状态转变的频率,得到状态转移矩阵(经济上也称支付矩阵),代表状态转变发生的可能性:

\begin{align}
    \bm{P}=
    \left[
    \begin{matrix}
        p_{11} & p_{12} & \cdots & p_{1r} \\
        p_{21} & p_{22} & \cdots & p_{2r} \\
        \vdots & \vdots & \ddots & \vdots \\
        p_{r1} & p_{r2} & \cdots & p_{rr} \\
    \end{matrix}
    \right]
    \notag
\end{align}

其中,p_{ij} 代表状态 X^{(i)} 转变到状态 X^{(j)} 发生的频率.

如果此时此刻各状态发生的概率所构成的向量已知:

\bm{\vec{\pi}}(n)=[\pi_1(n),\pi_2(n),\ldots,\pi_r(n)]

那么,接下来的状态发生概率向量则可以表示为:

\bm{\vec{\pi}}(n+1)=\bm{\vec{\pi}}(n)\bm{P}

也可以递归表出为:

\bm{\vec{\pi}}(n+1)=\bm{\vec{\pi}}(0)\bm{P}^{n+1}


Ⅲ. 示例

三位选手在比赛,裁判记录了得分情况如下:

局号

1

2

3

4

5

6

7

8

9

得分

3

3

2

3

1

2

1

3

1

例如第 7 局是选手 1 得分. 那么,状态转移链就可以写成:

332312131

转移矩阵则统计为:

\begin{align}
    \bm{P}=
    \begin{matrix}
         & 1 & 2 & 3 \\
        \hline
        1\ \vline & 0 & 1 & 1 \\
        2\ \vline & 1 & 0 & 1 \\
        3\ \vline & 2 & 1 & 1 \\
    \end{matrix}
    \Longleftrightarrow
    \begin{matrix}
         & 1 & 2 & 3 \\
        \hline
        1\ \vline & 0 & 0.5 & 0.5 \\
        2\ \vline & 0.5 & 0 & 0.5 \\
        3\ \vline & 0.5 & 0.25 & 0.25 \\
    \end{matrix}
    \notag
\end{align}

例如选手 3 转移到选手 1 一共发生 2 次,即有 2 次都是选手 3 输,随之选手 1 胜.

假设第十局三位选手得分情况未知,权且都标记为 33% 的概率得分,那么:

\bm{\vec{\pi}}(11)=\left[\dfrac{1}{3},\ \dfrac{1}{3},\ \dfrac{1}{3}\right]\times\bm{P}=\left[\dfrac{1}{3},\ \dfrac{1}{4},\ \dfrac{5}{12}\right]

简言之,接下来选手 1 有 33% 的概率得分,选手 2 有 25% 的概率得分,选手 3 有 41.67% 的概率得分.


Ⅳ. 代码

Markov.py
  1'''
  2# System --> Windows & Python3.10.0
  3# File ----> Markov.py
  4# Author --> Illusionna
  5# Create --> 2024/02/02 22:45:54
  6'''
  7# -*- Encoding: UTF-8 -*-
  8
  9# numpy==1.26.3
 10# pandas==2.0.3
 11
 12import os
 13import numpy as np
 14import pandas as pd
 15from typing import Literal
 16
 17def cls() -> None:
 18    os.system('')
 19    os.system('cls')
 20cls()
 21
 22
 23class MARKOV:
 24    """
 25    马尔可夫类.
 26    """
 27    def __init__(self, statuslink:Literal['状态链字符串, 例如: abbabaacaa']) -> None:
 28        """
 29        初始化构造函数.
 30        """
 31        self.__statuslink = statuslink
 32
 33    def __CountAdjacentStatusFrequency(
 34            statuslink:str,
 35            adjacentStatus:Literal['例如: 相邻状态字符串 ab, 表示 a -> b']
 36    ) -> int:
 37        """
 38        私有函数: 计算状态链中相邻状态 adjacentStatus 出现的频数.
 39        """
 40        adjacentStatusNumber = 0
 41        for i in range(0, len(statuslink)-1, 1):
 42            if statuslink[i:i+2] == adjacentStatus:
 43                adjacentStatusNumber = adjacentStatusNumber + 1
 44        return adjacentStatusNumber
 45
 46    def PayOffMatrix(self, probability:bool=False) -> pd.DataFrame:
 47        """
 48        公有函数: 计算支付矩阵.
 49        """
 50        uniqueItems = np.unique(list(self.__statuslink))
 51        df = pd.DataFrame(index=uniqueItems, columns=uniqueItems)
 52        for i in uniqueItems:
 53            for j in uniqueItems:
 54                df.loc[i, j] = MARKOV.__CountAdjacentStatusFrequency(self.__statuslink, i+j)
 55        if probability == True:
 56            df = df.div(df.sum(axis=1), axis='index')
 57        self.__df = df.div(df.sum(axis=1), axis='index')
 58        return df
 59
 60    @staticmethod
 61    def NextTransitionProbability(
 62            payOffMatrixProbability:pd.DataFrame,
 63            occurrenceProbability:dict|Literal['状态发生概率, 如字典形式 {a:0.5, b:0.3, c:0.2}'],
 64            step:int|Literal['接下来第 step 步, 默认为 1 步'] = 1,
 65            significant:int|Literal['小数点精度, 自然数, 如果异常, 可降低'] = 4
 66    ) -> np.array:
 67        """
 68        静态函数: 计算接下来的出现概率.
 69        """
 70        if len(occurrenceProbability.keys()) != payOffMatrixProbability.shape[0]:
 71            assert print(f'\033[3m\033[33m转移矩阵 {payOffMatrixProbability.shape[0]}x{payOffMatrixProbability.shape[0]} 维, 状态发生概率字典向量 1x{len(occurrenceProbability.keys())} 维, 无法相乘.\033[0m')
 72        tmp = 0
 73        L = []
 74        for (key, value) in occurrenceProbability.items():
 75            tmp = tmp + value
 76            L.append(value)
 77        try:
 78            np.testing.assert_approx_equal(tmp, 1.0, significant=significant)
 79        except:
 80            assert print(f'\033[3m\033[33m状态发生概率字典向量求和 {tmp} 不近似 1, 检查字典向量正确性或降低 significant={significant} 精度值\033[0m')
 81        del tmp
 82        output = np.array(L).dot(
 83            np.linalg.matrix_power(
 84                np.array(payOffMatrixProbability, dtype=np.float64),
 85                n = step
 86            )
 87        )
 88        return output
 89
 90
 91if __name__ == '__main__':
 92    obj = MARKOV(statuslink='332312131')
 93
 94    print(obj.PayOffMatrix(probability=False))
 95
 96    nextTransitionProbability = MARKOV.NextTransitionProbability(
 97        payOffMatrixProbability = obj.PayOffMatrix(probability=True),
 98        occurrenceProbability = {
 99            '1': 1/3,
100            '2': 1/3,
101            '3': 1/3
102        },
103        step = 1    # 如果步长很大, 则收敛情况下, 趋于转移矩阵的极限分布.
104    )
105    print(nextTransitionProbability)