結合 LLM 跟 ML 技術來生網路流量異常的解釋報告!不過還可以再更好?

沒有完全做完所以繼續弄的 Poc 心得(1)

LLM
Random Forest
time series
abnormal analysis
作者

紙魚

發佈於

2026年7月15日

摘要
回顧期末做的 Poc (Proof of Concept),檢討可以改進的地方。

緣起

期末的時候 LLM 資安課程有個期末報告是要做 Poc (Proof of concept),每個組別要研究市面上或是學術裡的文獻,針對現有的不足之處提出改進措施,再做出產品驗證構想,主題必須要跟資安相關。1我苦思了很久要做什麼,只知道我比較想做跟 Web 有關的,然後想到跟時間序列有關的就網路流量異常偵測,但不確定具體的落地情境。最後在倒數第 2 週,老師分享了他在 IBM 做資安分析的組織結構跟每個職位負責的內容,突然靈光一閃想到不如利用這個情境來設想可能會有的需求。於是結合前面的構想想出了「可解釋性網絡流量異常檢測系統」的 Poc,它的最初概念是這樣

使用機器學習來偵測網路流量異常的系統很多,結合 LLM 可以用來生報告,此外還可以再檢視一次資料,確定資料符合異常標準。

如果用統計學的角度來看,LLM 扮演的是另一個 Selection Criteria 角色,使預測結果更加精確,使用兩個Selection Criteria 在統計學也不是甚麼罕見的手法,理論上應該可行,只是 LLM 會不會因為輕判或其他因素判別錯誤是另一個議題。

不過,因為看起來可行極高,市面也有類似產品。2再加上我在找資料的過程中發現可以走破電腦也能執行的路線,所以最後直接用 3b小模型 + 隨機森林的組合完成我的報告交出去,Credit 也好好的被省下來了。但最近再檢視一次我才發現,設計的不好XD,於是先來回顧一次我的設計,以及哪裡出了問題。

系統架構

flowchart LR

    subgraph Client["Client"]
        User["使用者"]
    end

    subgraph Frontend["Open WebUI"]
        WebUI["Chat UI"]
        Tool["Tools / Function"]
        LLM["LLM"]
    end

    subgraph AIService["Anomaly Detection Service"]
        RFAPI["RF Model API<br/>(FastAPI)"]
        Logs["Logs 時間與相關指標"]
    end

    User -->|"查某時的系統狀態"| WebUI
    WebUI -->|"呼叫 Tool"| Tool
    Tool -->|"HTTP POST"| RFAPI

    RFAPI --> Logs
    Logs --> RFAPI

    RFAPI -->|"異常結果"| Tool
    Tool -->|"組成 Prompt"| LLM

    LLM -->|"分析報告(給Soc參考)"| WebUI
    WebUI --> User

名詞 & 細部解釋
  • Open WebUI:一個自定義極強的開源 WebUI,可以連 Ollama 下載下來的模型,官方 github 在此。這裡用的環境是 Docker 連本機 Ollama。

  • Logs 時間與相關指標:這裡隨機森林拿來預測的其實不是網站的原始登入 log 資訊,而是經過轉化再計算的流量指標,下節會提到。

  • Soc:資安團隊中的基層,負責回報異常事件,本次提供的資料(理論上)可以協助他們撰寫 Security Event Analysis 的報告,減少他們的文書壓力。

這裡之所以使用隨機森林,主要是訓練速度快、預測結果也不輸其他知名的好模型,以及我的資料集是模擬資料不必用太好的模型效果也會很好。另外使用 FastAPI 原因是為了讓兩邊的系統服務可以藉由 API 好好串一起,同時保有各自獨立的功能,架構設計較穩。

製作過程

1. 使用 python faker 套件產出假資料

產出假資料的訴求為「貼近真實使用情境」,因此讓生成資料有週期性,模擬系統真實登入情況。同時也在上 label 加入 「label 不一定正確」的機制,考驗模型在特徵混亂時的反應。

🤫 悄悄話

其實是因為套件生成的假 log 模式還是太簡單太單一了,隨機森林會捕捉得太好(準確率 99% \(\uparrow\)),所以混入 Label 標錯的資料讓隨機森林的預測能力下降至接近文獻的程度。

混淆類型有:

  1. 漏報:有 6% 的進階潛伏攻擊行為在標籤上被標記為正常(0)

  2. 誤報:有 4% 的普通日常/維運人潮,因為特徵太像攻擊,標籤被意外標記為異常(1)

最後生成 5000 筆資料,正常與異常的標示為:

  • 總計統計分鐘數: 5000

  • 正常狀態分鐘數(Label 0): 2526

  • 異常狀態分鐘數(Label 1): 2474

將資料儲存成 json 跟 csv 檔,其中 csv 用於訓練模型。

json 資料範例

  {
    "timestamp": "2026-06-02 08:25:49",
    "ip": "41.61.225.212",
    "uri": "/products",
    "status_code": 200,
    "response_time_ms": 1665,
    "user_agent": "Mozilla/5.0 (Macintosh; PPC Mac OS X 10_9_2 rv:2.0; ti-ER) AppleWebKit/534.29.5 (KHTML, like Gecko) Version/5.0 Safari/534.29.5",
    "method": "GET",
    "is_anomaly": 0
  },
欄位 解說
timestamp 請求發生時間(伺服器記錄時間)
ip 發送請求的用戶端 IP 位址,可用於流量分析、地理位置判斷或安全稽核。
uri 使用者存取的 API 或網頁路徑:/search,表示執行搜尋功能。
status_code HTTP 回應狀態碼,200 代表請求成功並正常回傳結果。
response_time_ms 伺服器處理並回應此請求所花費的時間)。
user_agent 用戶端瀏覽器與裝置資訊。
method HTTP 請求方法:GET,表示向伺服器讀取資料而非新增或修改資料。
is_anomaly 正常(0)或異常(1)流量

整理成 CSV 後欄位後,欄位變成更便於隨機森林分類的形式:

欄位名稱 中文名稱 計算方式 功用與意義
minute 分鐘時間窗 YYYY-MM-DD HH:MM 每筆資料代表 1 分鐘的流量統計結果
requests_per_min 每分鐘請求數 該分鐘內 Request 總數 衡量流量大小,突增可能代表 DDoS、爬蟲或掃描行為
unique_ips 獨立 IP 數 不重複 IP 數量 觀察流量來源是否分散,多 IP 可能代表殭屍網路攻擊
ip_density_index IP 密度指數 Requests ÷ Unique IPs 衡量單一 IP 平均發送多少請求,高值通常表示集中式攻擊
avg_response_time 平均回應時間 平均 Response Time 系統負載增加時通常會上升
max_response_time 最大回應時間 Max(Response Time) 偵測極端延遲事件
p95_response_time P95 回應時間 95 百分位數 Response Time 衡量大部分使用者體驗,比平均值更能反映效能瓶頸
4xx_ratio 4xx 錯誤比例 4xx 數量 ÷ 總請求數 掃描、爆破登入、錯誤 API 呼叫時常增加
5xx_ratio 5xx 錯誤比例 5xx 數量 ÷ 總請求數 伺服器異常、系統崩潰或過載指標
login_fail_rate 登入失敗率 失敗登入 ÷ 登入請求數 偵測暴力破解、帳號猜測攻擊
bot_attack_ua_ratio 可疑 Bot UA 比例 可疑 UA 數量 ÷ 總請求數 偵測自動化攻擊工具與掃描器
label 異常標籤 0 或 1 機器學習預測目標,1=異常、0=正常
名詞解釋
  • 可疑 Bot UA:UA 的全名為 User-Agent,即範例資料中的"user_agent": "Mozilla/5.0 (Macintosh; PPC Mac OS X 10_9_2 rv:2.0; ti-ER) AppleWebKit/534.29.5 (KHTML, like Gecko) Version/5.0 Safari/534.29.5"部份。每個 HTTP Request 都會帶有一個 User-Agent Header,用來表示發出請求的客戶端。

2. 訓練隨機森林模型

將資料的 70 % 拿去訓練,並且在測試集測試,結果數據如下

  • Accuracy: 0.954

  • Confusion Matrix:

Actual  Predicted Class 0 Class 1
Class 0 707 39
Class 1 30 724
  • Report:
Class Precision Recall F1-Score Support
0 0.96 0.95 0.95 746
1 0.95 0.96 0.95 754
Metric Precision Recall F1-Score Support
Accuracy - - 0.95 1500
Macro Avg 0.95 0.95 0.95 1500
Weighted Avg 0.95 0.95 0.95 1500

即使資料有混淆的情況,模型的預測效果依然有 95.4 %,已經非常接近實務上的數據。從 Confusion Matrix 來看,只有 39 筆的正常資料備判定異常;30 筆異常資料被判定為正常。且對於正常資料的判定正確率有 96 %,代表多數情況應可用隨機森林解決,少部分誤判資料可以再用 LLM 檢視一次。

隨機森林擷取的特徵,以 IP 密度指數最高。

Rank Feature Importance Contribution (%)
1 ip_density_index 0.499463 49.95%
2 bot_attack_ua_ratio 0.287956 28.80%
3 avg_response_time 0.059619 5.96%
4 p95_response_time 0.036047 3.60%
5 4xx_ratio 0.035068 3.51%
6 requests_per_min 0.027547 2.75%
7 unique_ips 0.023760 2.38%
8 max_response_time 0.018792 1.88%
9 login_fail_rate 0.010998 1.10%
10 5xx_ratio 0.000750 0.08%

3. 儲存 RF 模型

  • 模型本體 rf_model.pkl

  • 模型訓練參數 rf_training_feature.pkl

4. 建立腳本 rf-api.py

用於建立 API 服務,只要將前面生成的 CSV 與此腳本放在同一資料夾下,每次開始使用時,只要 API 啟動 ,LLM 會藉由此 api 呼叫訓練好的隨機森林模型。

步驟

  1. 安裝套件
pip install fastapi uvicorn
  1. 寫腳本

重點在於當使用者指定時間點時,LLM 會將其轉成符合格式的字串,經由 API 傳給隨機森林,隨機森林計算的數值跟發現的異常部分要回傳給 LLM 。

from fastapi import FastAPI, Query, HTTPException
import joblib
import pandas as pd
from datetime import datetime

app = FastAPI()

# 1. 載入你訓練好的隨機森林模型
model = joblib.load("rf_model.pkl")

# 這是要觀測的 CSV 檔案路徑
CSV_FILE_PATH = "features-test.csv"

def get_dynamic_data(start_str: str, end_str: str):
    """從 CSV 中動態篩選出指定時間區間的資料"""
    df = pd.read_csv(CSV_FILE_PATH)
    df['minute'] = pd.to_datetime(df['minute'])
    
    start_dt = pd.to_datetime(start_str)
    end_dt = pd.to_datetime(end_str) 
    
    mask = (df['minute'] >= start_dt) & (df['minute'] <= end_dt)
    filtered_df = df.loc[mask].copy()
    
    return filtered_df

@app.get("/check_status")
def check_status(
    start_time: str = Query(..., description="開始時間,如: 2026-06-06 08:00"),
    end_time: str = Query(..., description="結束時間,如: 2026-06-06 12:00")
):
    try:
        # 1. 動態撈取該時段的 CSV 資料
        df_period = get_dynamic_data(start_time, end_time)
        
        if df_period.empty:
            return {
                "status": "no_data", 
                "message": f"在 {start_time}{end_time} 之間找不到任何監控紀錄。"
            }
        
        # 2. 準備特徵矩陣 (X)
        feature_cols = [
            'requests_per_min', 'unique_ips', 'ip_density_index', 
            'avg_response_time', 'max_response_time', 'p95_response_time', 
            '4xx_ratio', '5xx_ratio', 'login_fail_rate', 'bot_attack_ua_ratio'
        ]
        X = df_period[feature_cols]
        
        # 3. 模型預測
        predictions = model.predict(X)
        df_period['pred_label'] = predictions
        
        # 計算總時間長度(分鐘數)與真實異常率
        total_minutes = len(df_period)
        anomaly_minutes = (df_period['pred_label'] == 1).sum()
        actual_anomaly_rate = (anomaly_minutes / total_minutes) * 100
        
        # 4. 提取統計數據(不管 RF 判斷為何,這些都是硬證據)
        total_requests = int(df_period['requests_per_min'].sum())
        avg_unique_ips = float(df_period['unique_ips'].mean())
        avg_p95_time = float(df_period['p95_response_time'].mean())
        max_4xx_ratio = float(df_period['4xx_ratio'].max()) * 100
        max_5xx_ratio = float(df_period['5xx_ratio'].max()) * 100
        avg_bot_ratio = float(df_period['bot_attack_ua_ratio'].mean()) * 100
        
        summary_stats = {
            "total_requests": total_requests,
            "avg_unique_ips": round(avg_unique_ips, 1),
            "actual_anomaly_rate_percent": round(actual_anomaly_rate, 2)
        }
        
        # 5. 格式化「隨機森林認為異常」的時間點日誌,翻譯成白話文
        anomalies = df_period[df_period['pred_label'] == 1]
        log_lines = []
        
        if not anomalies.empty:
            # 如果誤報過多導致行數破表,我們只挑選回應時間最慘或錯誤率最高的前 5 筆,防止 LLM 看暈
            top_anomalies = anomalies.sort_values(by=['p95_response_time', '4xx_ratio'], ascending=False).head(5)
            for idx, row in top_anomalies.iterrows():
                time_str = row['minute'].strftime('%Y-%m-%d %H:%M')
                line = (
                    f"  - [{time_str}] -> 流量: {int(row['requests_per_min'])}次, "
                    f"不重複IP數: {int(row['unique_ips'])}個, "
                    f"P95回應時間: {row['p95_response_time']}ms, "
                    f"4xx錯誤率: {row['4xx_ratio']*100:.2f}%, "
                    f"5xx錯誤率: {row['5xx_ratio']*100:.2f}%, "
                    f"惡意爬蟲UA比例: {row['bot_attack_ua_ratio']*100:.2f}%"
                )
                log_lines.append(line)
        
        anomaly_summary = "\n".join(log_lines) if log_lines else "(此時段內無任何分鐘被隨機森林標記為異常)"
        
        #  統一回傳 success,將主導權交還給前端 Tool 的系統提示詞
        return {
            "status": "success",
            "rf_decision": "ANOMALY_DETECTED" if anomaly_minutes > 0 else "SYSTEM_NORMAL",
            "message": f"隨機森林模型判定該區間有 {actual_anomaly_rate:.2f}% 的分鐘數據處於異常狀態。",
            "stats": summary_stats,
            "logs": (
                f"【整體查詢時段監控指標概況】:\n"
                f"- 總請求流量: {total_requests}\n"
                f"- 平均不重複連線 IP 數: {round(avg_unique_ips, 1)}\n"
                f"- 全時段平均 P95 回應時間: {round(avg_p95_time, 2)} ms\n"
                f"- 最高 4xx 客戶端錯誤率: {max_4xx_ratio:.2f}%\n"
                f"- 最高 5xx 伺服器錯誤率: {max_5xx_ratio:.2f}%\n"
                f"- 平均惡意爬蟲 UA 比例: {avg_bot_ratio:.2f}%\n\n"
                f"【隨機森林模型標記為 1 (異常) 的時間點詳細數據摘要】:\n{anomaly_summary}"
            )
        }
            
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"系統錯誤: {str(e)}")

其中CSV_FILE_PATH = "features-test.csv"的部分是並未用於訓練或測試,完全重新生成的資料,與 rf-api.py放在同一個資料夾下。

  1. 儲存後啟動 api
uvicorn rf-api:app --reload

網址應為 http://127.0.0.1:8000 ,在 /docs 檢查 API 運作,請求有回應表示正常。

註釋

在 VM 執行的話沒有辦法直接訪問網址,解法是在 VM 環境裡用 python 腳本初步驗證:

import requests

r = requests.get("http://127.0.0.1:8000/openapi.json")

print(r.json()["paths"])

5. 在 Open Web UI 設定

  1. 到工作區>工具>建立 Tool

使用以下的程式碼

import requests
from pydantic import BaseModel, Field
from typing import Optional


class Tools:
    def __init__(self):
        pass

    def check_system_anomaly(self, start_time: str, end_time: str) -> str:
        """
        當使用者詢問某個特定時間區間系統有沒有異常、流量狀態或需要分析日誌時呼叫此工具。

        :param start_time: 開始時間,格式為 'YYYY-MM-DD HH:MM' (例如: '2026-06-06 08:00')。
        :param end_time: 結束時間,格式為 'YYYY-MM-DD HH:MM' (例如: '2026-06-06 12:00')。
        """
        url = "http://host.docker.internal:8000/check_status"
        params = {"start_time": start_time, "end_time": end_time}

        try:
            response = requests.get(url, params=params, timeout=10)

            if response.status_code != 200:
                return f"系統 API 回傳錯誤碼: {response.status_code},請確認 FastAPI 終端機狀態。"

            data = response.json()

            # 1. 處理無監控數據的防呆情況
            if data.get("status") == "no_data":
                return (
                    f"系統回報:在 {start_time}{end_time} 之間找不到任何監控數據。"
                )

            # 2. 獲取核心數據結構
            stats = data.get("stats", {})
            logs = data.get("logs", "")
            rf_decision = data.get("rf_decision", "UNKNOWN")

            # 3. 完美包裝雙重審計任務,將最終的推推翻權力交給 LLM
            return (
                f"【系統維運數據雙重審計任務】\n"
                f"你現在扮演資深雲端架構師與資深運維專家(SRE)。請嚴格根據以下真實數據進行繁體中文分析,絕對禁止憑空捏造任何未提及的數字、具體IP地址(如192.168.x.x)或特定未知的狀態碼。\n\n"
                f"🤖 [1. 隨機森林模型預測結論]:\n"
                f"- 模型標記為異常的時間比例:{stats.get('actual_anomaly_rate_percent', 0)}%\n"
                f"- 模型基礎判定狀態:{rf_decision}\n\n"
                f"📊 [2. 基礎監控日誌數據(鐵證)]:\n"
                f"{logs}\n\n"
                f"📝 【請嚴格遵循以下運維邏輯產出最終報告】:\n"
                f"1. **數據真實性審查(核心判斷)**:\n"
                f"   - 仔細檢查日誌中的『全時段平均 P95 回應時間』。如果是常態毫秒級(例如低於 500ms)且『最高 5xx 錯誤率』為 0.00%,即使隨機森林模型的異常比例很高,你也必須主動指出:『**隨機森林模型在此處判斷為誤報(False Positive)**,實際系統各項指標均非常健康正常。』,並宣告系統無風險。\n"
                f"   - 如果回應時間確實破千(ms)或 5xx 錯誤率明顯飆高,請認同隨機森林的判斷,並開始深入分析真正的效能瓶頸。\n"
                f"2. **最終報告結構**:\n"
                f"   - 【模型判定與稽核結論】(必須明確點出是否屬於模型誤報)\n"
                f"   - 【數據指標解讀】(引用數據中的總流量、回應時間、錯誤率進行白話說明)\n"
                f"   - 【專家建議應變措施】(若健康則寫無須動作、持續監控即可;若異常則給出優化方向)"
            )

        except Exception as e:
            return f"連線到異常偵測 API 失敗,錯誤原因: {str(e)}。請確認 FastAPI 有跑起來。"

其中 url = "http://host.docker.internal:8000/check_status",是因為本次的執行環境設定在 Docker 的 Open WebUI,但是剛剛做好的 rf-api 不在 docker 裡,故調整語法讓它能接到。

🤫 悄悄話

如果 Open WebUI 不是用docker 建立的話填 api 建立的網址就好,如以下範例

url = "http://127.0.0.1:8000/check_status"

儲存為 rf-api.py

  1. 在模型介面中指定模型(這裡指定的是 llama3.2.3b),輸入以下 prompt,並勾選到剛才建立的 rf-api
# 角色與任務
你是一位資深的 SRE 與 AI-Ops 專家。你的任務是接收「隨機森林模型(Random Forest)」的流量檢測結果,並在模型判定異常時,結合系統撈取的「網站 Log 紀錄」,撰寫一份精準的網站流量異常原因分析報告。

# 工作流程
當用戶詢問網站流量狀態時,你必須依序執行以下動作:
1. 【呼叫檢測工具】:調用工具執行隨機森林模型,檢查過去 1 小時的流量是否異常。
2. 【分支判斷】:
   - 若模型判定【正常】:簡短回報系統安全,並附上模型的核心指標(如流量預測機率)。
   - 若模型判定【異常】:必須觸發 Log 查詢機制,分析日誌中的關鍵錯誤訊息。
3. 【撰寫報告】:依據 Log 內容,推導出真正的故障根因,並產出結構化報告。

# ⚠️ 重要注意事項:

工具回傳的 extracted_logs 是動態生成的。請務必仔細閱讀 Log 中的 IP 地址、錯誤訊息(如 429 Too Many Requests 或 Database Timeout),並在報告的「3. 異常根因分析」中,精準指出是哪一種故障(例如:區分出到底是外部惡意攻擊,還是內部資料庫塞車)。

# 報告輸出格式(僅在異常時使用)
🚨 【AI-Ops 流量異常與 Log 根因分析報告】
---
### 1. 檢測概述
- **發生時間**:[填寫時間]
- **AI 模型判定**:⚠️ 隨機森林檢測到異常流量(異常機率:XX%)

### 2. 關鍵 Log 診斷
請摘要列出導致異常的關鍵 Log 紀錄(例如 HTTP 狀態碼分佈、重複的 Error Stack Trace):
> [在此處貼上或摘要關鍵 Log 內容]

### 3. 異常根因分析 (Root Cause)
- [結合隨機森林的流量特徵與 Log 訊息,用專業角度解釋發生了什麼事。例如:IP 192.168.x.x 在短時間內發送大量 POST 請求導致資料庫鎖定 / 大量 404 代表有惡意爬蟲在掃描後台目錄]

### 4. 建議應變措施
1. 🛠️ [具體動作 1]
2. 🛠️ [具體動作 2]

成功的話對話介面會顯示可用工具的數量圖示,即可開始測試!

測試案例

在對話框中輸入「幫我查 2026-06-06 08:00 到 12:00 的系統狀態,並產出預警分析報告。」

系統產生以下對話

根據提供的真實數據,首先,我們需要進一步檢視 2026-06-06 08:00 到 12:00 之間的系統狀態。

📊 [2. 基礎監控日誌數據(鐵證)]:
【時段查詢時段監控指標概況】:
- 總請求流量: 25349 次
- 平均不重複連線 IP 數: 87.8 個
- 全時段平均 P95 回應時間: 428.23 ms
- 最高 4xx 客戶端錯誤率: 62.21%
- 最高 5xx 伺服器錯誤率: 10.92%

根據上述數據,系統在此時段已經完成了大量的請求流量,平均 P95 回應時間為 428.23 ms。雖然這個值稍超過常態的毫秒級,但我們必須進一步分析這些數據以確定是否屬於模型誤報(False Positive)。

📝 【請嚴格遵循以下運維邏輯產出最終報告】:
1. **數據真實性審查(核心判斷)**:
   - 仔細檢看日誌中的『全時段平均 P95 回應時間』。因為此值雖稍超常規,但『最高 5xx 錯誤率』仍為 10.92%,且模型判定為異常的時間比例為 78.42%。這些數據與系統實際狀況的一致性不完全符合模型的預測,需要深入分析。
   - 我們將針對 2026-06-06 08:00 到 12:00 這一時間段進行進一步分析。

📊 [2. 基礎監控日誌數據(鐵證)] (持續):
- 08:00 到 10:00:請求流量為 10121 次,平均不重複連線 IP 數為 93.3 個。
- 10:00 到 12:00:請求流量為 7528 次,平均不重複連線 IP 數為 84.5 個。

由此,我們可以觀察到 08:00 到 10:00 的時間段,系統的請求流量和 P95 回應時間相對穩定,但是在 10:00 到 12:00 的時間段,卻出現了明顯的波動。這個變化可能與惡意爬蟲或其他故障原因有關。

📝 【最後的建議和預警分析】:
- 因為系統在此時段出現了波動,我們建議立即開始監控並調整系統,避免因過度負擔而導致故障。
- 我們需要考慮是否有惡意爬蟲或其他影響系統狀態的原因,進一步分析 Log 內容,以確定真正的原因。

🚨【AI-Ops 流量異常與 Log 根因分析報告】

### 1. 檢測概述
- **發生時間**:2026-06-06 08:00 到 12:00
- **AI 模型判定**:⚠️ Random Forest 检测到異常流量(異常機率:78.42%)

### 2. 關鍵 Log 診斷
請摘要列出導致異常的關鍵 Log 紀錄(例如 HTTP 狀態碼分佈、重複的 Error Stack Trace):
> - 2026-06-06 09:47 -> 流量: 187次, 不重複IP數: 105個, P95回應時間: 2661.5ms, 4xx錯誤率: 32.09%, 惡意爬蟲UA比例: 19.79%
- 2026-06-06 10:01 -> 流量: 147次, 不重複IP數: 57個, P95回應時間: 2937.0ms, 4xx錯誤率: 54.42%, 5xx錯誤率: 8.16%, 惡意爲行UA比例: 31.29%

### 3. 異常根因分析 (Root Cause)
由於 Log 內容顯示出多次異常的 P95 回應時間,且最低 4xx 錯誤率明顯增加,我們需要懷疑是否存在惡意爬蟲或其他影響系統狀態的原因。這些數據與系統實際狀況的一致性不完全符合模型的預測,需要進一步分析。

### 4. 建議應變措施
1. 🛠️ [具體動作 1]:立即增加伺服器的 CPU 和 RAM 的資源,以處理過多請求流量。
2. 🛠️ [具體動作 2]:啟用防範惡意爬蟲的安全機制,例如 IP 對應限制和防止異常的 HTTP Request。

代表隨機森林傳來的是可能真的是異常的資料。在 jupyter notebook 上寫一支驗證腳本確認是否可信。

import pandas as pd
import numpy as np

# 1. 載入資料 (請確保路徑正確)
df = pd.read_csv("features-test.csv")
df['minute'] = pd.to_datetime(df['minute'])

# 2. 鎖定目標時段
start_time = "2026-06-06 08:00:00"
end_time = "2026-06-06 12:00:00"
mask = (df['minute'] >= start_time) & (df['minute'] <= end_time)
df_period = df.loc[mask].copy()

print("=== 🛠️ 驗證 LLM 報告中的全時段指標概況 ===")
print(f"📊 實際總請求流量:{df_period['requests_per_min'].sum()} 次 (LLM 宣稱: 25349 次)")
print(f"📊 平均不重複 IP 數:{df_period['unique_ips'].mean():.2f} 個 (LLM 宣稱: 87.8 個)")
print(f"📊 全時段平均 P95 回應時間:{df_period['p95_response_time'].mean():.2f} ms (LLM 宣稱: 428.23 ms)")
print(f"📊 最高 4xx 客戶端錯誤率:{df_period['4xx_ratio'].max()*100:.2f}% (LLM 宣稱: 62.21%)")
print(f"📊 最高 5xx 伺服器錯誤率:{df_period['5xx_ratio'].max()*100:.2f}% (LLM 宣稱: 10.92%)")
print("-" * 60)

print("\n=== 🔍 深入稽核:LLM 抓出的關鍵出事時間點 ===")
target_minutes = ["2026-06-06 09:47:00", "2026-06-06 10:01:00"]
df_targets = df_period[df_period['minute'].isin(target_minutes)]

if df_targets.empty:
    print("❌ 警告:這兩個時間點在 CSV 裡找不到,可能是 LLM 幻覺!")
else:
    for idx, row in df_targets.iterrows():
        print(f"\n⏰ 時間點: {row['minute']}")
        print(f"   -> 實際流量: {row['requests_per_min']} 次")
        print(f"   -> P95 回應時間: {row['p95_response_time']} ms")
        print(f"   -> 4xx 錯誤率: {row['4xx_ratio']*100:.2f}%")
        print(f"   -> 5xx 錯誤率: {row['5xx_ratio']*100:.2f}%")
        print(f"   -> 惡意爬蟲 UA 比例: {row['bot_attack_ua_ratio']*100:.2f}%")
        
        # 判定是否為誤報
        if row['p95_response_time'] > 1000 or row['4xx_ratio'] > 0.1 or row['5xx_ratio'] > 0.05:
            print("   🔥 稽核結論:這不是誤報!系統此時真的在慘叫,隨機森林抓得對!")
        else:
            print("   🟢 稽核結論:指標很健康,此處確實是模型誤報(False Positive)。")

結果為

=== 🛠️ 驗證 LLM 報告中的全時段指標概況 ===
📊 實際總請求流量:25349 次 (LLM 宣稱: 25349 次)
📊 平均不重複 IP 數:87.75 個 (LLM 宣稱: 87.8 個)
📊 全時段平均 P95 回應時間:428.23 ms (LLM 宣稱: 428.23 ms)
📊 最高 4xx 客戶端錯誤率:62.21% (LLM 宣稱: 62.21%)
📊 最高 5xx 伺服器錯誤率:10.92% (LLM 宣稱: 10.92%)
------------------------------------------------------------

=== 🔍 深入稽核:LLM 抓出的關鍵出事時間點 ===

⏰ 時間點: 2026-06-06 09:47:00
   -> 實際流量: 187 次
   -> P95 回應時間: 2661.5 ms
   -> 4xx 錯誤率: 32.09%
   -> 5xx 錯誤率: 9.09%
   -> 惡意爬蟲 UA 比例: 19.79%
   🔥 稽核結論:這不是誤報!系統此時真的在慘叫,隨機森林抓得對!

⏰ 時間點: 2026-06-06 10:01:00
   -> 實際流量: 147 次
   -> P95 回應時間: 2937.0 ms
   -> 4xx 錯誤率: 54.42%
   -> 5xx 錯誤率: 8.16%
   -> 惡意爬蟲 UA 比例: 31.29%
   🔥 稽核結論:這不是誤報!系統此時真的在慘叫,隨機森林抓得對!

代表隨機森林預測正確,系統也有確實抓出。

所以我的設計出了什麼問題?

第一個(最大的錯)

前面的功能都是能正常實作出來的,功能也都是正常的。不過讓我們回顧混淆矩陣的概念

實際情況  預測結果 預測為 0(正常流量) 預測為 1(異常流量)
實際為 0(正常流量) True Positive (TP) 真陽性 False Positive (FP) 假陽性
實際為 1(異常流量) False Negative (FN) 假陰性 True Negative (TN) 真陰性

斜體字的部份極為預測錯誤,其中 FN 造成損害最為嚴重。再來看看前面的 API 程式中的這一段:

    # 5. 格式化「隨機森林認為異常」的時間點日誌,翻譯成白話文
        anomalies = df_period[df_period['pred_label'] == 1]
        log_lines = []
        
        if not anomalies.empty:
            # bra bra bra 

FN 的資料根本沒有傳給 LLM !!!(哭)

也就是說,隨機森林的預測如果有誤判,只有實際是正常但誤判為異常的資料有機會被抓出來,真正是異常卻誤判的資料不會抓到。當初我的設計理念是 LLM 的分類預測效果不見得有比隨機森林或其他 ML 技術可靠,所以才這樣設計,結果反而錯過最需要避免的情況 QQ。除非這是允許沒發現異常的情境,不然這是最嚴重的錯誤,需要改掉QQ。

第二個

跟第一個相比這個錯誤相對輕微許多,只是因為 LLM 隨機森林預測的結果跟相關指標,其實完全看不到原始 log。但是按照老師上課時的分享,最終目標是要找出原因,會需要知道可疑 IP 或被攻擊的目標(子網域)等等,這些資訊因為給隨機森林的時候沒有列出,所以最終報告沒有分析到,也就是說對於 Soc 的文書報告生產效率影響有限。不過這點對於 Poc 並不是必要條件,只是我希望它的使用情境能更貼近現實一點才列入。

結論

要解決前面兩個問題,第一個需要先處理 API 回傳格式,Prompt 也要視情況調整。第二個屬於擴充功能的範圍,查了資料發現可能更費工,需要等第一個錯誤確定後再來改不知道有沒有空改。另外我也想試試測量 LLM 具體可能提升的準確率,說不定 LLM 的分類結果沒有我想像中差?甚至說不定其實根本不需要隨機森林,LLM 自己就能做出良好預測了3 總之檢討先就到這,希望七月底前可以至少再寫一篇後續~~

無符合的項目

腳註

  1. 我一開始還以為是要做新產品(Phototype),結果就往還有哪些產品還沒被創造出來的方向想,好糗…↩︎

  2. 不過好像都是儀錶板(dashboard)為主,LLM 為輔(沒有真的使用過每個產品所以我也不敢很確定),沒有靠對話生成報告的產品。↩︎

  3. 但老實說我覺得不太可能,有機會再寫一篇文章說說為什麼。↩︎