交通規劃四階段法:基於 Python 的交通分佈預測演算法復現 - 附完整程式碼連結

多玩我的世界盒子發表於2024-04-09

目錄
  • 交通規劃四階段法:基於 Python 的交通分佈預測演算法復現 - 附完整程式碼連結
    • 我只是想使用這些程式碼
      • 下載程式碼檔案
      • 程式碼的使用方法
    • 合作
    • 部分程式碼內容的展示

交通規劃四階段法:基於 Python 的交通分佈預測演算法復現 - 附完整程式碼連結

我這個學期有交通規劃的課程。·交通規劃四階段法中第二階段即是交通分佈預測,需要使用一些常用的演算法,常見的像是:

  1. 傳統的增長係數方法:平均係數法、底特律法、Frater法、Funess法等
  2. 重力模型:無約束重力模型、單約束重力模型法、雙約束重力模型法

我寫了一些演算法簡單的 Python 實現,封裝成了 Python 類和函式,以便呼叫。當然,實際的交通需求預測當中不會真的使用 Python 之類的程式碼工具來實現,有現成的專用工具,如 TransCAD 等。此處程式碼僅做一演示,以便 課堂測試計算的時候偷懶 學習交流使用。

到目前為止演算法的完成度並不高,只有簡單的幾個模型。對於已經完成的程式碼部分,大家可以自行取用。但是 比起分享現在已經完成的程式碼,我更希望有人可以和我一起合作寫程式碼。因此我將專案釋出掛在了 Gitee 平臺,理想的話希望可以發展成一個 Python 包。

專案的頁面地址:基於 Python 的交通分佈預測演算法復現

拉取專案的命令:

git clone https://gitee.com/BOXonline_1396529/traffic_distribution_predict.git

我只是想使用這些程式碼

如果您只是想使用這些程式碼來學習演算法或者完成您的課堂作業,以下是你需要做的事:

下載程式碼檔案

看這篇部落格的小夥伴們應該有很多都是和我一樣的苦逼交通生,可能從來沒有在開放程式碼平臺下載過程式碼,找不到下載按鈕。其實下載按鈕就在網頁的這個位置:

image

最近的 Gitee 有點煩人,可能會讓你掃碼關注公眾號註冊之類的。稍微應付一下就好了哈。

點選之後會彈出這個視窗:

image

直接下載 .zip 就是程式碼的壓縮包了。

程式碼的使用方法

為防止交通專業的同學們不會用這些程式碼,特此說明:你可以像使用一般的模組那樣使用這些程式碼。確保專案根目錄下的 traffic_distribution_predict 目錄與您需要呼叫模組的程式碼檔案(.py.ipynb 等)處於同一目錄下。匯入需要的工具,這裡以福萊特法交通分佈預測函式為例:

from traffic_distribution_predict.coefficient_model import frator

然後呼叫函式:

# 需要計算的資料
# `X` 為 OD 矩陣
# `U` 和 `V` 分別為未來年的生成量與吸引量
X = np.array([[17,7,4],[7,38,6],[4,5,17]])
U = np.array([38.6, 91.9, 36.0])
V = np.array([39.3, 90.3, 36.9])
# `fX` 為未來年 OD 矩陣
fX = frator(X, U, V, alpha=0.05) 

具體的用法可以參考專案根目錄下的 .\docs\build\html\index.html 檔案,雙擊在瀏覽器中開啟。也可以參考詳細的程式碼註釋。

合作

如果您想要參與專案合作,您可以:

  1. 與我取得聯絡
  2. 提交 Issues
  3. 克隆倉庫到本地,編寫程式碼,提交 Pull Requests

部分程式碼內容的展示

以下是部分程式碼內容的展示,選取了 無約束重力模型 作為演示。

"""
traffic_distribution_predict.gravity_model
==========================================

實現基於 **重力模型** 的交通分佈預測。

在交通分佈預測中,重力模型(Gravity model)是一種廣泛應用
的模型,其靈感來源於物理學中的萬有重力定律。在交通領域,這個
模型用於預測從一個地區到另一個地區的流量(如人口流動、交通流
量等)。

模型假設兩地區之間的流量與兩地區的吸重力成正比,與兩地區之間
的距離的某個函式成反比。
"""

import numpy as np

import statsmodels.api as sm

class unconstrained_gravity_model:
    r""" 
    使用無約束重力模型進行交通分佈預測
    
    這個類實現了使用 **無約束重力模型** 進行交通分佈預測的功能

    無約束重力模型的基本形式(無約束重力模型)可以表示為:
    
    .. math::
        
        q_{ij} = k \frac{O_i^{\alpha} O_j^{\beta}
        }{C_{ij}^{\gamma}}
        
    無約束重力模型包含 :math:`\alpha`、:math:`\beta`、
    :math:`\gamma`,引數透過引數估計方法取得。具體的引數估計
    方法是對模型兩側得取對數,從而轉化為線性迴歸模型的形式:
    
    .. math::
        
        \ln(q_{ij}) = {
        \ln(\alpha) + \beta \ln(Oi \cdot Dj) - {
            \gamma \ln(c_{ij})}}
        
    令上式中各個引數項:
    
    - :math:`y = \ln(q_{ij})`
    - :math:`a_0 = \ln(\alpha)`
    - :math:`a_1 = \beta`
    - :math:`a_2 = -\gamma`
    
    則可對應如下的線性迴歸模型:
    
    .. math::
        
        Y = a_0 + a_1 x_1 + a_2 x_2
    
    透過對線性模型進行求解和引數的轉換,可以實現無約束重力模型的
    引數估計。其中,線性模型的求解依賴 `statsmodels.api.OLS`
    ,您有必要安裝 `statsmodels`。
    
    使用下面的命令安裝 `statsmodels`:
    
    .. code-block:: shell
    
        pip install statsmodels
    
    輸入引數
    ----------
    X : numpy.array
        交通原始的 OD 矩陣,要求行列數相等
    C : numpy.array
        交通現狀下各個交通小區之間往返所需的行駛時間,是交通
        距離的量化,要求行列數相等
        
    類的方法
    ----------
    __init__(X, C) :
        類的初始化方法
    fit() :
        擬合模型
    OLS_summary() : 
        輸出一元線性迴歸模型的詳細資訊
    predict(U, V, fC):
        預測未來年的交通 OD 矩陣,將直接輸出 OD 矩陣
        
    示例
    ----------
    假設已經存在 3*3 的 OD 矩陣 `X` 和 代表各個交通小區之間往返
    所需的行駛時間的矩陣 `C`,可以建立模型:
    
    >>> X = np.array([[17,7,4],[7,38,6],[4,5,17]])
    >>> C = np.array([[7,17,22],[17,15,23],[22,23,7]])
    >>> gravity_model = unconstrained_gravity_model(X, C)
    
    使用 `.fit()` 方法可以擬合模型,直接返回的元組即模型的三
    引數:math:`\alpha`、:math:`\beta`、:math:`\gamma`,
    逐一對應。
    
    >>> gravity_model.fit()
    (0.12445664474836608, 
     1.1726892457872755, 
     1.4553127410580864)
    
    如果需要模型根據當前年的交通現狀預測未來年的交通分佈,則需要
    未來年各個交通小區的交通發生量 `U`、吸引量 `V`,以及未來年
    各個交通小區之間往返所需的行駛時間的矩陣 `fC`
    
    >>> fC = np.array([[4,9,11],[9,8,12],[11,12,4]])
    >>> U = np.array([38.6, 91.9, 36.0])
    >>> V = np.array([39.3, 90.3, 36.9])
    
    呼叫 `.predict()` 進行預測
    
    >>> gravity_model.predict(U, V, fC)
    array([[ 88.94742489,  72.49109653,  18.95286558],
           [ 75.57580647, 237.96479061,  46.18126501],
           [ 18.80408686,  43.94860253,  76.12489132]])
    
    其他參照
    ----------
    :class:`statsmodels.api.OLS`
        `statsmodels` 提供的線性迴歸工具
    
    """
    
    def __init__(self, X, C):
        """ 
        類的初始化方法

        輸入引數
        ----------
        X : numpy.array, m=n
            交通原始的 OD 矩陣
        C : numpy.array, m=n
            交通現狀下各個交通小區之間往返所需的行駛時間
        """
        # 將函式引數給到類屬性
        self.OD_mat = X
        self.distance_mat = C
        
        # 取得交通小區的個數,交通小區個數即 OD 矩陣長度
        self.n = len(self.OD_mat)
    
        # 計算總吸引量和總生成量
        self.O = np.sum(self.OD_mat, axis=1) # 橫向求和
        self.D = np.sum(self.OD_mat, axis=0) # 縱向求和
        
    def fit(self):
        """
        擬合模型

        返回值
        -------
        tuple
            包含重力模型三引數的元組,形如:
            `(self.alpha, self.beta, self.gamma)`
            
        示例
        -------
        
        >>> gravity_model.fit()
        (0.12445664474836608, 
         1.1726892457872755, 
         1.4553127410580864)
        """
        # `x_1` 和 `x_2` 兩個列表就能儲存取對數後的數值
        # 這樣的變數命名方式是為了和數學公式裡的表述對應以便理解
        x_1 = [] # 對 $O_i$ 和 $D_j$ 取對數的結果
        x_2 = [] # 對距離矩陣 `distance_mat` 每一項取對數
        y = [] # 對原始 OD 矩陣取對數
        
        # 透過迴圈填充列表
        for i in range(self.n):
            for j in range(self.n):
                y.append(np.log(self.OD_mat[i][j]))
                x_1.append(np.log(self.O[i]*self.D[j]))
                x_2.append(
                    np.log(self.distance_mat[i][j])
                    )
        
        # 組織訓練線性迴歸模型的資料
        train_X = np.array([x_1, x_2]).T
        train_y = y
        
        # `statsmodels.api.OLS` 預設沒有截距項 
        # 這裡需要手動加入截距 
        train_X_with_bias = sm.add_constant(train_X)
        self.results = sm.OLS( # 注意引數排列的順序
            train_y, train_X_with_bias
            ).fit() # 直接擬合模型
        
        # 獲取線性迴歸模型引數
        self.OLS_params = self.results.params
        
        # 根據公式計算模型引數
        self.alpha = np.e ** (self.OLS_params[0])
        self.beta = self.OLS_params[1]
        self.gamma = -self.OLS_params[2]
        
        self.params = (self.alpha, self.beta, self.gamma)
        
        return self.params
    
    def predict(self, U, V, fC):
        """
        利用擬合好的模型,根據當前年的交通現狀預測未來年的交通分佈

        輸入引數
        ----------
        U : numpy.array
            未來年各交通小區的總生成量,行向量
        V : numpy.array
            未來年各交通小區的總吸引量,行向量
        fC : numpy.array
            各個交通小區之間往返所需的行駛時間

        返回值
        -------
        numpy.array
            預測的未來年各交通小區交通量
            
        示例
        -------
        如果需要模型根據當前年的交通現狀預測未來年的交通分佈,則需要
        未來年各個交通小區的交通發生量 `U`、吸引量 `V`,以及未來年
        各個交通小區之間往返所需的行駛時間的矩陣 `fC`
        
        >>> fC = np.array([[4,9,11],[9,8,12],[11,12,4]])
        >>> U = np.array([38.6, 91.9, 36.0])
        >>> V = np.array([39.3, 90.3, 36.9])
        
        呼叫 `.predict()` 進行預測
        
        >>> gravity_model.predict(U, V, fC)
        array([[ 88.94742489,  72.49109653,  18.95286558],
               [ 75.57580647, 237.96479061,  46.18126501],
               [ 18.80408686,  43.94860253,  76.12489132]])

        """
        self.future_O = U
        self.future_D = V
        self.future_distance_mat = fC
        
        self.q = np.zeros(self.OD_mat.shape)
        
        # 根據公式計算 q_ij
        for i in range(self.n):
            for j in range(self.n):
                self.q[i][j] = (
                    self.alpha * (
                        ((self.future_O[i] * self.future_D[j]) ** self.beta
                            ) / (
                        self.future_distance_mat[i][j] ** self.gamma
                        )))
        
        return self.q

相關文章