TOPSIS (理想解相似度順序偏好法)


Posted by Mephisto on 2025-02-26

0. Motivation

就是覺得有趣,在想可不可以用到午餐的推薦上面?
TOPSIS(Technique for Order Preference by Similarity to Ideal Solution)是一種多準則決策分析方法,適用於需要在多個評估指標下對多個方案進行排序和選擇的情境。這邊主要簡述使用情境,之後紀錄計算的流程步驟,最後使用 Python 給出一個範例。

1. 使用情境

1-1. 適用情境

  • 多目標決策問題: 當需要在多個評估指標下對多個方案進行綜合評估時,TOPSIS方法能有效地將各指標進行標準化處理,計算各方案與理想解的距離,從而進行排序。
    例子: 在供應商選擇中,企業需要考慮成本、品質、交貨時間等多個因素。TOPSIS方法可以綜合這些指標,評估各供應商的表現,幫助企業選擇最合適的供應商。

  • 指標數據已知且可靠: 當各評估指標的數據已知且具有可靠性時,TOPSIS方法可以充分利用這些原始數據的信息,精確地反映各方案之間的差距。
    例子: 在產品品質評估中,若各產品的性能指標(如耐用性、功耗、效率等)數據已知且可靠,TOPSIS方法可以根據這些數據對產品進行排序,協助決策者選擇最佳產品。

1-2. 不適用情境

  • 指標數據缺乏或不可靠: 當評估指標的數據缺乏或不可靠時,TOPSIS方法可能無法提供準確的評估結果。
    例子: 在新產品開發初期,許多性能指標的數據可能尚未確定或不完整,此時使用TOPSIS方法可能導致評估結果偏差。

  • 指標之間存在高度相關性: 當評估指標之間存在高度相關性或多重共線性時,TOPSIS方法可能會受到影響,因為它假設各指標是相互獨立的。
    例子: 在評估城市發展水平時,若同時使用人均收入和GDP作為指標,這兩者之間可能存在高度相關性,使用TOPSIS方法可能會放大某一方面的影響,導致評估結果失真。

  • 決策者無法確定指標權重: TOPSIS方法需要為各評估指標分配權重,若決策者無法確定各指標的重要性,可能影響評估結果的準確性。
    例子: 在公共政策評估中,若涉及經濟、環境、社會等多個方面,且決策者無法確定各方面的相對重要性,使用TOPSIS方法可能難以得出公正的結論。

2. TOPSIS 計算步驟

Step 1. 建立一個決策矩陣 $A$

並且 $1\leq i\leq n, 1\leq j\leq m$。

說明 :

  1. $n$ 個評估的對象
  2. $m$ 個評估的指標
  3. 每一行(column)為一個指標,每一列(row)為一個評估對象的數據

Step 2. 無因次化(Nondimensionalization)、標準化數據

消除指標間不同因次的影響,將指標數據標準化 :

Step 3. 計算加權標準化決策矩陣

標準化後的數據乘以對應的權重 $w_{ij}$ :

其中 :

  • $w_j$ 是指標 $j$ 的權重,這邊不細講權重的計算,下面的範例將使用熵權法(Entropy Weight method)

並且我們因此得到加權標準化決策矩陣 :

Step 4. 確定理想解(PIS)與反理想解(NIS)

  • 理想解(Positive Ideal Solution) : 取加權標準化決策矩陣中每個指標的最大值

  • 反理想解(Negative Ideal Solution) : 取加權標準化決策矩陣中每個指標的最小值

Step 5. 計算與PIS與NIS的歐氏距離

其中 :

  • $d_i^+$ 為樣本 $i$ 到 PIS 的距離
  • $d_i^-$ 為樣本 $i$ 到 NIS 的距離

Step 6. 計算相對相似度

計算每個評估對象相對於理想解的相似度:

其中 :

  • $f_{i}$ 為對象樣本 $i$ 的相似度,數值範圍為 $[0,1]$。
  • 當 $f_i$ 越接近 1 ,表示評估的對象越好;當 $f_i$ 越接近 0,表示評估的對象越差。

3. Python Example

這邊使用的範例是《數學建模:算法與編程實現》的河流水質評價的範例 :

import numpy as np

# 設定全域浮點數輸出精度為小數點後四位
np.set_printoptions(precision=4)


def read_csv_with_numpy(file_path, delimiter=',', skip_header=0, dtype=float, encoding='utf-8'):
    """
    使用 numpy 讀取 CSV 檔案

    參數:
        file_path  : 檔案路徑
        delimiter  : 分隔符號 (預設為 ',')
        skip_header: 跳過的標題列數 (預設為 0)
        dtype      : 資料型別 (預設為 float)
        encoding   : 檔案編碼 (預設為 'utf-8')

    返回:
        讀取到的 numpy 陣列,若失敗則返回 None
    """
    try:
        with open(file_path, 'r', encoding=encoding) as file:
            data = np.genfromtxt(file, delimiter=delimiter, skip_header=skip_header, dtype=dtype)
        return data
    except UnicodeDecodeError as e:
        print(f"讀取檔案時發生編碼錯誤:{e}")
        return None
    except Exception as e:
        print(f"讀取檔案時發生錯誤:{e}")
        return None


def transform_indicator(data, indicator_type, target=None, interval=None):
    """
    根據指標類型對數據進行轉換。

    參數:
        data           : 待轉換數據 (numpy 陣列)
        indicator_type : 指標類型,可選 'positive' (正向)、'negative' (負向)、'center' (居中型)、'interval' (區間型)
        target         : 居中型指標的目標值 (必須提供)
        interval       : 區間型指標的上下限 tuple,例如 (lower_bound, upper_bound)

    返回:
        轉換後的數據 (numpy 陣列)
    """
    if indicator_type == 'positive':
        # 正向指標:數值越大越好,無需轉換
        return data
    elif indicator_type == 'negative':
        # 負向指標:數值越小越好,轉換為正向指標 (極小極大化轉換)
        max_val = np.max(data)
        return max_val - data
    elif indicator_type == 'center':
        # 居中型指標:數值越接近目標值越好
        if target is None:
            raise ValueError("居中型指標需要提供目標值 target")
        M_Value = np.max(np.abs(target - data))
        return 1 - (np.abs(target - data)) / M_Value
    elif indicator_type == 'interval':
        # 區間型指標:數值在指定區間內最好,超出區間者給予懲罰
        if interval is None or len(interval) != 2:
            raise ValueError("區間型指標需要提供上下限值的 tuple,例如 (lower_bound, upper_bound)")
        lower_bound, upper_bound = interval
        dt_tmp = np.ones_like(data, dtype=float)
        M_low_value = lower_bound - np.min(data)
        M_up_value = np.max(data) - upper_bound
        M_Value = np.max([M_low_value, M_up_value])
        dt_tmp[data < lower_bound] = 1 - (lower_bound - data[data < lower_bound]) / M_Value
        dt_tmp[data > upper_bound] = 1 - (data[data > upper_bound] - upper_bound) / M_Value
        return dt_tmp
    else:
        raise ValueError("未知的指標類型:{}".format(indicator_type))


def entropy_weight_method(data):
    """
    使用熵權法計算每個指標的權重。

    參數:
        data: numpy.ndarray,形狀為 (n, m),其中 n 為評估對象數量,m 為評估指標數量。

    返回:
        weights: numpy.ndarray,各指標的權重 (形狀: (m,))
    """
    # 定義縮放區間 [0.002, 0.996]
    lower_bound = 0.002
    upper_bound = 0.996

    # 第一步:對每個欄位進行最小-最大正規化
    min_vals = np.min(data, axis=0)
    max_vals = np.max(data, axis=0)
    range_vals = max_vals - min_vals
    # 避免除以零,若範圍為 0 則設為 1
    range_vals[range_vals == 0] = 1

    # 將數據正規化到 [0, 1]
    normalized_data = (data - min_vals) / range_vals
    # 再縮放到 [0.002, 0.996]
    scaled_data = normalized_data * (upper_bound - lower_bound) + lower_bound

    n, m = scaled_data.shape
    # 計算各指標下各樣本的比重 pij
    p = scaled_data / np.sum(scaled_data, axis=0)

    epsilon = 1e-10  # 為避免 log(0) 的錯誤
    # 計算熵值
    e = -np.sum(p * np.log(p + epsilon), axis=0) / np.log(n)
    # 計算熵冗餘度
    d = 1 - e
    # 計算權重,並標準化使得權重和為 1
    weights = d / np.sum(d)

    return weights


def topsis_analysis(data, weights):
    """
    使用 TOPSIS 方法進行多準則決策分析。

    參數:
        data   : numpy.ndarray,處理後的決策矩陣 (形狀: (n, m))
        weights: numpy.ndarray,各指標的權重 (形狀: (m,))

    返回:
        relative_closeness: numpy.ndarray,每個方案的 TOPSIS 得分 (已重新縮放至 [0, 100])
    """
    # 第一步:標準化決策矩陣 (向量規範化)
    norm_data = data / np.sqrt(np.sum(data ** 2, axis=0))

    # 第二步:計算加權標準化矩陣
    weighted_data = norm_data * weights

    # 第三步:確定正理想解與負理想解
    ideal_best = np.max(weighted_data, axis=0)  # 正理想解:各指標最大值
    ideal_worst = np.min(weighted_data, axis=0)  # 負理想解:各指標最小值

    # 第四步:計算各方案與正/負理想解的歐幾里得距離
    dist_to_best = np.sqrt(np.sum((weighted_data - ideal_best) ** 2, axis=1))
    dist_to_worst = np.sqrt(np.sum((weighted_data - ideal_worst) ** 2, axis=1))

    # 第五步:計算相對貼近度得分
    relative_closeness = dist_to_worst / (dist_to_best + dist_to_worst)

    # 將得分重新縮放到 [0, 100]
    min_score = np.min(relative_closeness)
    max_score = np.max(relative_closeness)
    if max_score - min_score != 0:
        relative_closeness = (relative_closeness - min_score) / (max_score - min_score) * 100
    else:
        relative_closeness = np.full_like(relative_closeness, 0)

    return relative_closeness


# 主程式執行區
if __name__ == '__main__':
    # 設定資料檔案路徑
    file_path = 'river_data.csv'

    # 讀取 CSV 檔案 (跳過標題列)
    data_array = read_csv_with_numpy(file_path, skip_header=1)

    if data_array is not None:
        # 提取各欄位資料
        river_id = data_array[:, 0].astype(int)  # 第一欄:河流編號 (轉換為整數)
        oxygen_content = data_array[:, 1]  # 第二欄:含氧量
        ph_value = data_array[:, 2]  # 第三欄: pH 值
        bacteria_count = data_array[:, 3]  # 第四欄:細菌總數 (個數/ml)
        nutrients = data_array[:, 4]  # 第五欄:植物性營養物量 (ppm)

        # 輸出原始資料
        print("河流編號:", river_id)
        print("含氧量:", oxygen_content)
        print("pH 值:", ph_value)
        print("細菌總數(個數/ml):", bacteria_count)
        print("植物性營養物量(ppm):", nutrients)

        # 依據各指標的特性進行數據預處理
        oxygen_content_dt = transform_indicator(oxygen_content, indicator_type='positive')
        ph_value_dt = transform_indicator(ph_value, indicator_type='center', target=7)
        bacteria_count_dt = transform_indicator(bacteria_count, indicator_type='negative')
        nutrients_dt = transform_indicator(nutrients, indicator_type='interval', interval=(10, 20))

        # 合併處理後的各指標數據,並四捨五入至小數點後五位
        processed_data = np.round(np.array([oxygen_content_dt, ph_value_dt, bacteria_count_dt, nutrients_dt]).T, 5)
        print("\n處理後的決策矩陣:")
        print(processed_data)

        # 利用熵權法計算各指標權重
        weights = entropy_weight_method(processed_data)
        print("\n各指標的權重:")
        print(weights)

        # 進行 TOPSIS 分析,計算每個方案的相對貼近度得分 (TOPSIS 得分已縮放至 [0, 100])
        scores = topsis_analysis(processed_data, weights)
        print("\n各方案的 TOPSIS 得分 (已縮放到 [0, 100]):")
        for i, score in enumerate(scores):
            print(f"河流編號: {river_id[i]}, TOPSIS 得分: {score:.2f}")

資料 (river_data.csv):

河流 含氧量(ppm) PH值 細菌總數(個/ml) 植物性營養物量(ppm)
1 4.69 6.59 51 11.94
2 2.03 7.86 19 6.46
3 9.11 6.31 46 8.91
4 8.61 7.05 46 26.43
5 7.13 6.50 50 23.57
6 2.39 6.77 38 24.62
7 7.69 6.79 38 6.01
8 9.30 6.81 27 31.57
9 5.45 7.62 5 18.46
10 6.19 7.27 17 7.51
11 7.93 7.53 9 6.52
12 4.40 7.28 17 25.30
13 7.46 8.24 23 14.42
14 2.01 5.55 47 26.31
15 2.04 6.40 23 17.91
16 7.73 6.14 52 15.72
17 6.35 7.58 25 29.46
18 8.29 8.41 39 12.02
19 3.54 7.27 54 3.16
20 7.44 6.26 8 28.41

結果 :


#MathematicalModel #TOPSIS







Related Posts

Web Monetization 簡介

Web Monetization 簡介

[Node.js] Global 物件

[Node.js] Global 物件

CS50 linked list

CS50 linked list


Comments