跳到主要內容

DE Day 7 查詢計畫與索引:EXPLAIN 實戰

DE Day 7 查詢計畫與索引:EXPLAIN 實戰

執行需求:CPU 可跑。今天是 SQL 進階區塊的最後一篇。我們會用 DuckDB 1.4 帶你看「查詢計畫(query plan)」這項寫 SQL 時最重要的除錯工具,並在資料倉儲場景下展示索引(index)的正確用法。學會讀 EXPLAIN 之後,你會具備一項關鍵能力:寫完一段查詢後,能說出「這段要走什麼演算法」、「哪個步驟是瓶頸」、「加索引是否能加速」。這也是資料工程師比純分析師更具價值的差異點。

引言

很多寫 SQL 的人知道 EXPLAIN 這個詞,但沒實際讀過它的輸出。原因通常是:門檻太高、看不懂、文件太長。今天我們要把門檻降低到「看了就能用」的程度。我們從三個基本觀念出發:第一,EXPLAIN 顯示的是資料庫「打算如何」執行查詢;第二,EXPLAIN ANALYZE 顯示的是「實際執行」的結果與耗時;第三,索引是查詢效能加速的主要工具,但「何時建索引」是一門學問,建錯索引甚至會拖慢查詢。

今天的目標有四個:第一,理解 DuckDB EXPLAIN 輸出的三個關鍵區塊:邏輯計畫、最佳化計畫、實際執行;第二,能用 EXPLAIN ANALYZE 看出查詢的「行數估計」、「執行時間」、「記憶體使用」三項指標;第三,建立一份「中型規模的測試資料集」(約 100 萬列),展示「無索引 vs 有索引」的執行時間差異;第四,理解索引的代價:寫入變慢、占用空間,並學會「先量測、再決定」的索引建立準則。整個系列 Day 14、Day 32 的品質監控會用到今天的觀念。

EXPLAIN 的三種模式

DuckDB 提供三種 EXPLAIN 變體:

  • EXPLAIN <SELECT>:只顯示最佳化後的邏輯計畫,不實際執行。
  • EXPLAIN ANALYZE <SELECT>:實際執行查詢,並把執行時間印出來。
  • EXPLAIN FORMAT=TREE/JSON/HTML:改變輸出格式。預設是文字。

當你想要「看預定路徑、不真的跑查詢」時用 EXPLAIN;當你想要「真的跑、看實際數字」時用 EXPLAIN ANALYZE。第三個選項是格式選擇,後續使用時偏好 TREE(用樹狀印出)方便閱讀。

完整實作:建立中型測試資料集

我們先用 DuckDB 的 range() 函式建立一份 100 萬列的測試資料:

"""DE Day 7:EXPLAIN 與索引實戰。"""
import duckdb
import time

con = duckdb.connect()
con.execute("""
    CREATE OR REPLACE TABLE demo_events AS
    SELECT
        i AS event_id,
        'u' || LPAD(CAST((i % 1000) AS VARCHAR), 4, '0') AS user_id,
        DATE '2024-01-01' + INTERVAL (i % 365) DAY AS event_date,
        i % 7 AS event_type,
        random() AS value
    FROM range(1000000) AS t(i)
""")
print("資料筆數:", con.execute("SELECT COUNT(*) FROM demo_events").fetchone()[0])
# 輸出:資料筆數:1000000

這段用 range(1000000) 產生 0 到 999999 的整數序列,再加上對應的衍生欄位。user_id 由 i % 1000 決定,所以只會有 1000 個不重複的使用者;event_date 在 2024 年內展開;value 是隨機浮點數。這份資料集足以展示索引的效果,並讓 EXPLAIN ANALYZE 的執行時間有意義(不至於跑太快、也不會跑太久)。

先做一個沒有索引的基準查詢,看執行計畫:

start = time.perf_counter()
no_index_plan = con.execute(
    "EXPLAIN ANALYZE "
    "SELECT COUNT(*), AVG(value) FROM demo_events "
    "WHERE event_date BETWEEN DATE '2024-06-01' AND DATE '2024-06-30'"
).fetchall()
elapsed = time.perf_counter() - start
print(f"未建索引查詢耗時(秒):{elapsed:.4f}")
print("執行計畫摘要:")
for row in no_index_plan:
    text = row[0] if isinstance(row[0], str) else str(row[0])
    # 取前 5 行做摘要
    for line in text.splitlines()[:6]:
        print(f"  {line}")
# 輸出:未建索引查詢耗時(秒):例如 0.0800
# 輸出:執行計畫摘要:
# 輸出:  EXPLAIN ANALYZE SELECT COUNT(*), AVG(value) FROM demo_events WHERE event_date...
# 輸出:  ┌─────────────────────────────────────┐
# 輸出:  │┌─────────────────────────────────┐│
# 輸出:  ││    Ungrouped Aggregate          ││
# 輸出:  ││        ...                      ││
# 輸出:  │└─────────────────────────────────┘│

這段使用 EXPLAIN ANALYZE 對一條簡單查詢做完整執行,並把執行時間量出來。DuckDB 預設把執行計畫印成巢狀樹狀圖,可以用 print 拿到整段文字。從計畫裡可以看到如 Ungrouped Aggregate、HASH_JOIN、SEQ_SCAN 等關鍵字,這些就是「邏輯計畫節點」。在百萬列的測試資料下,DuckDB 即使沒索引也能輕鬆跑出 100ms 內的時間,這是因為它是欄式引擎;對列式儲存(MySQL、PostgreSQL),這個查詢可能要數秒。

EXPLAIN 輸出的關鍵區塊

DuckDB 的 EXPLAIN ANALYZE 輸出大概分四個區塊。每個區塊都有特定用途:

analyze_text = con.execute(
    "EXPLAIN ANALYZE "
    "SELECT user_id, COUNT(*) "
    "FROM demo_events "
    "WHERE event_date BETWEEN DATE '2024-06-01' AND DATE '2024-06-30' "
    "GROUP BY user_id ORDER BY user_id LIMIT 5"
).fetchall()[0][0]
print(analyze_text)

這段是其中一段典型輸出(簡化版):

EXPLAIN ANALYZE SELECT user_id, COUNT(*) FROM demo_events ...

┌────────────────────────────────────────────────┐
│┌──────────────────────────────────────────────┐│
││              Query Plan                       ││
│└──────────────────────────────────────────────┘│
└────────────────────────────────────────────────┘
┌────────────┴────────────┐
│      PROJECTION         │
│    ─────────────────    │
│       PROJECTION       │
│      ────────────      │
│       user_id          │
│       count_star()     │
│   ──────────────        │
│        100001          │
│    (0.04s, 0.05s)      │
└────────────┬────────────┘

可以從輸出讀到三件事:第一,每個節點(PROJECTION、SEQ_SCAN 等)顯示它處理了多少列、用了多少時間;第二,DuckDB 把時間拆成「自身耗時」與「子節點耗時」兩個欄位,方便定位瓶頸;第三,整棵樹從下往上讀:最底層是實際從磁碟讀資料的掃描節點,越往上越聚合、彙總、最後到投影。若要繼續閱讀請用 EXPLAIN FORMAT=TREE 拿到更結構化輸出,或 FORMAT=JSON 拿到樹狀 JSON 結構,方便做工具讀取。

建索引並比較執行時間

我們在 event_date 上建一個索引,然後重新跑同一段查詢看差異:

con.execute("CREATE INDEX IF NOT EXISTS idx_event_date ON demo_events(event_date)")

start = time.perf_counter()
with_index_plan = con.execute(
    "EXPLAIN ANALYZE "
    "SELECT COUNT(*), AVG(value) FROM demo_events "
    "WHERE event_date BETWEEN DATE '2024-06-01' AND DATE '2024-06-30'"
).fetchall()
elapsed = time.perf_counter() - start
print(f"建索引後查詢耗時(秒):{elapsed:.4f}")
print("執行計畫摘要有 INDEX_JOIN 或 INDEX_SCAN:")
for row in with_index_plan:
    text = row[0] if isinstance(row[0], str) else str(row[0])
    has_index = "INDEX" in text
    print(f"  計畫含 INDEX 標記:{has_index}")
    for line in text.splitlines()[:10]:
        if line.strip():
            print(f"    {line}")
# 輸出:建索引後查詢耗時(秒):例如 0.0250
# 輸出:  計畫含 INDEX 標記:True
# 輸出:    EXPLAIN ANALYZE ...
# 輸出:    ┌─────────────────────────────────────┐
# 輸出:    │ PROJECTION ...                      │
# 輸出:    │   ...                              │
# 輸出:    │ INDEX_SCAN ...                     │
# 輸出:    │  ...                              │

建好索引後,可以看到執行計畫裡出現 INDEX_SCAN 標記,這表示 DuckDB 從「全表掃描」改為「索引掃描」。在 100 萬列規模下,DuckDB 對日期範圍查詢原本已經很快(因為它是欄式引擎),但索引仍能在某些情境下明顯加速。在百萬列等級的 row-store 系統(如 PostgreSQL),這個差距會是「秒 vs 毫秒」的層級。

我們再對一個聚合查詢做比較,看索引能否加速 GROUP BY:

queries = [
    (
        "每日彙總(聚合時無索引加速作用)",
        "SELECT event_date, COUNT(*) FROM demo_events GROUP BY event_date ORDER BY event_date LIMIT 5"
    ),
    (
        "使用者單查詢",
        "SELECT * FROM demo_events WHERE user_id = 'u0123' LIMIT 5"
    ),
    (
        "日期 + 使用者雙條件",
        "SELECT * FROM demo_events WHERE event_date = DATE '2024-06-15' AND user_id = 'u0420' LIMIT 5"
    ),
]

print("比較三類查詢:")
for label, q in queries:
    start = time.perf_counter()
    result = con.execute("EXPLAIN ANALYZE " + q).fetchall()
    elapsed = time.perf_counter() - start
    snippet = result[0][0] if isinstance(result[0][0], str) else str(result[0][0])
    plan_uses_index = "INDEX" in snippet
    print(f"  {label}")
    print(f"    耗時(秒):{elapsed:.4f} 使用索引:{plan_uses_index}")
# 輸出:比較三類查詢:
# 輸出:  每日彙總(聚合時無索引加速作用)
# 輸出:    耗時(秒):例如 0.0400 使用索引:False
# 輸出:  使用者單查詢
# 輸出:    耗時(秒):例如 0.0050 使用索引:True
# 輸出:  日期 + 使用者雙條件
# 輸出:    耗時(秒):例如 0.0030 使用索引:True

這個比較告訴我們三件事:第一,GROUP BY 掃描全部資料,索引對它沒幫助;第二,特定 WHERE user_id = ... 查詢走索引,明顯加速;第三,WHERE event_date AND user_id 雙條件查詢,DuckDB 會選擇最有選擇性的索引。在實務上,「建了索引就一定會被用到」是錯的;只有當查詢條件命中索引欄位且選擇性高時才會加速。

建立與刪除索引的指令

DuckDB 的索引建立與刪除語法跟 PostgreSQL 非常相似:

con.execute("CREATE INDEX IF NOT EXISTS idx_user_id ON demo_events(user_id)")
print("索引建立完成")

con.execute("DROP INDEX IF EXISTS idx_event_date")
print("索引刪除完成")

print("列出目前所有索引:")
indices = con.execute(
    "SELECT index_name, table_name, sql FROM duckdb_indexes()"
).fetchall()
for idx in indices:
    print(f"  {idx[0]} (table={idx[1]})")
# 輸出:索引建立完成
# 輸出:索引刪除完成
# 輸出:列出目前所有索引:
# 輸出:  idx_user_id (table=demo_events)

CREATE INDEX IF NOT EXISTS 確保重複執行時不會跳錯誤,是管線腳本的標準寫法。DROP INDEX IF EXISTS 同理。duckdb_indexes() 是 DuckDB 內建函式,回傳所有索引的清單,包含 index_name、table_name、sql(建立該索引的 SQL 文字)。這個寫法可以寫進 Day 32 品質監控,讓你每天列出「現在有哪些索引」,在索引被意外刪掉時發出告警。

在儲存成本考量下還有一個小細節:ART 索引在小資料集上雖然便宜,但當索引欄位是字串(例如 event_name VARCHAR(50))且資料有上億列時,每個索引可能佔數 GB。這是一個值得在 Day 35 效能與成本章節再做量化分析的主題。今天先記得查詢計畫裡 INDEX_SCAN 出現的次數,每多一個就是多一份儲存與寫入成本。

SET 對執行計畫的影響

DuckDB 提供幾個 SET 來影響執行計畫。我們看三個常見的:

print("thread 數對執行計畫的影響:")
for threads in [1, 4, 8]:
    con.execute(f"SET threads={threads}")
    start = time.perf_counter()
    result = con.execute(
        "EXPLAIN ANALYZE "
        "SELECT event_date, COUNT(*), AVG(value) "
        "FROM demo_events GROUP BY event_date"
    ).fetchall()
    elapsed = time.perf_counter() - start
    print(f"  threads={threads} 耗時(秒):{elapsed:.4f}")

# 輸出:thread 數對執行計畫的影響:
# 輸出:  threads=1 耗時(秒):例如 0.0900
# 輸出:  threads=4 耗時(秒):例如 0.0350
# 輸出:  threads=8 耗時(秒):例如 0.0290

con.execute("RESET threads")
print("已還原為預設 threads 設定")

從這個結果可以看到多執行緒的效果:threads=1 比 threads=8 慢約三倍,這在大型彙總查詢上會更明顯。RESET threads 還原預設值。實務上請依 CPU 核心數設定,建議是實體核心數。CI 環境或 Docker 容器則要看容器被配置的核心數,預設可能只有 1-2 核。

另一個 SET 是 SET memory_limit,當大規模查詢時限制記憶體避免吃光整台機器:

con.execute("SET memory_limit='1GB'")
print("設定記憶體上限:", con.execute("SELECT current_setting('memory_limit')").fetchone()[0])
# 輸出:設定記憶體上限:1.00GB

con.execute("RESET memory_limit")
print("已還原預設記憶體設定")

本系列在大型管線(例如 Day 30-35 端到端)會大量用 SET 預先做好資源配置,避免在 CI 或容器裡硬撐到 OOM 為止。這個寫法在 Day 35 效能與成本章節會再用一次,今天先建立觀念。

什麼時候建索引?什麼時候不要建?

資料倉儲場景下,索引的決策跟 OLTP 場景相反。OLTP 系統(例如銀行交易、訂票系統)每筆交易都會寫入,所以索引是必要的取捨;資料倉儲(DuckDB、BigQuery、Redshift)多為批次讀寫,索引的價值較低。我們用一張決策表總結:

情境 是否建索引 備註
小資料表(< 10 萬列) 不必 全表掃描本就極快,索引反而增加寫入成本
OLTP 場景查詢頻繁、寫入少 應該建 索引可明顯加速點查
資料倉儲批次分析、寫入少 選擇性建 僅針對常用過濾欄位建
高基數欄位(使用者 ID、訂單 ID) 可建 選擇性高,索引收益大
低基數欄位(性別、狀態) 不必 選擇性低,索引幾乎沒幫助
經常 GROUP BY 的欄位 可考慮 視資料分布而定
資料傾斜(極少數值佔多數) 謹慎 索引可能把集中資料讀慢

這張表的重點是「先量測,再決定」。同一份資料配上不同查詢,索引效果差異極大。先用 EXPLAIN ANALYZE 找瓶頸,再決定要不要加索引。

另一個 SET 是 SET enable_optimizer 與 SET disabled_optimizers。當你想關閉某個特定的查詢計畫最佳化時,這兩個指令很好用:

con.execute("SET disabled_optimizers='filter_pushdown,statistics_propagation'")
print("已關閉 filter_pushdown 與 statistics_propagation 兩項最佳化")
con.execute("RESET disabled_optimizers")
print("已還原為預設最佳化設定")

這個寫法在你懷疑「某項最佳化導致查詢變慢」時可以暫時關閉驗證。例如懷疑 filter_pushdown 把 WHERE 條件推得太深造成錯誤結果時,可以暫時關閉,看計畫變成怎樣。RESET 把這項還原為預設。實務上這個手法比較少用,但學會了當遇到神秘慢查詢時會很有用,整個系列在 Day 35 效能與成本章節會再示範一次。

常見錯誤與踩雷

第一個雷:把 EXPLAIN ANALYZE 當成「量測查詢時間」的工具,但忘了它「會真的執行查詢」。當你的查詢會寫入資料(INSERT、UPDATE、DELETE),EXPLAIN ANALYZE 會實際執行副作用,可能造成重複寫入或環境污染。請只在 SELECT 上用 EXPLAIN ANALYZE。

第二個雷:閱讀 EXPLAIN 時只看最上層的 PROJECTION 節點,忽略下層的 SEQ_SCAN 或 HASH_JOIN。瓶頸通常在最底層,因為那是實際讀取資料的節點。閱讀樹狀圖時要從下往上,先看最下層的讀取成本,再看上層的彙總成本。

第三個雷:盲目相信優化器的選擇。DuckDB 與 PostgreSQL 多數情況下會選對策略,但當統計資訊過時(特別是 ANALYZE 沒跑過時),最佳化器可能選到次佳計畫。在 DuckDB 1.4 對小資料表會偏向全表掃描(因為「全表掃描成本低於開索引」),這是合理的;當資料量變大後仍會改用索引。如果你對計畫有疑慮,可以加上 SET disabled_optimizers='filter_pushdown' 之類的設定暫時關閉最佳化,看效果。

第四個雷:在 DuckDB 1.4 建太多索引。DuckDB 的索引不是用 B-tree,而是用 ART(Adaptive Radix Tree)。雖然 ART 在空間上比 B-tree 更精簡,但每多一個索引,INSERT / UPDATE 時就要多寫一個索引,會拖慢批次寫入。實務建議:先把資料匯入(落地),再做 ANALYZE,最後才建索引。

第五個雷:用 EXPLAIN 看不出「為什麼慢」。當 EXPLAIN ANALYZE 顯示某節點耗時 0.05 秒,但你的查詢總耗時 1 秒時,請把 FORMAT=JSON 加上並且列出所有子節點的耗時,會發現真正慢的可能在 SEQ_SCAN 或 FILTER 而不是 PROJECTION。

效能與實務提醒

EXPLAIN 與 EXPLAIN ANALYZE 在 DuckDB 1.4 非常快,不到毫秒就能產出計畫。實務上請把它寫進 CI 流程:在 PR 提交時自動跑關鍵查詢的 EXPLAIN ANALYZE,把耗時超出閾值的查詢標記為需要調整。這個做法在 Day 32 品質監控章節會完整展開,今天先把工具準備好。

另一個實務提醒:DuckDB 不像 PostgreSQL 有自動 VACUUM 與 ANALYZE。當你對一份大資料表頻繁刪除列或更新時,索引可能與資料不一致,需要偶爾 DROP INDEX + CREATE INDEX 重建。Day 30 端到端管線會設計一個「重建索引」的工作。

最後一個提醒:EXPLAIN 在不同的引擎有不同輸出格式。DuckDB、PostgreSQL、SQL Server、MySQL、BigQuery 各自有專屬的視覺化工具:DuckDB 有 DuckDB CLI 的樹狀輸出;PostgreSQL 有 EXPLAIN (FORMAT JSON) 接 pev2 視覺化;MySQL 有 EXPLAIN FORMAT=TREE。請依目標平台選擇對應的閱讀方式。整個系列在 Day 30 的管線會用 DuckDB CLI 為主、其他平台為輔。

小結

今天把 SQL 進階區塊的最後一篇工具學完。我們用 100 萬列的測試資料展示了 EXPLAIN、EXPLAIN ANALYZE 的輸出形式,並實際建立索引與刪除索引,看到索引對點查的加速效果、對 GROUP BY 沒幫助的限制、以及多執行緒對彙總查詢的加速。整個索引決策準則是「先量測、再決定」,用 EXPLAIN ANALYZE 量測當前耗時,再決定是否建索引,並且避免在低基數欄位上浪費空間。

把今天學到的關鍵詞抄進筆記本:EXPLAIN、EXPLAIN ANALYZE、FORMAT=TREE/JSON、SEQ_SCAN、INDEX_SCAN、HASH_JOIN、PROJECTION、AGGREGATE、ART、基數(cardinality)、選擇性、SET threads、SET memory_limit。明天開始進入工具區塊:第一個工具是 DuckDB,從「本機 OLAP 神器」這個角色切入,看它的核心能力、CLI、檔案格式與整合用法。

結語

EXPLAIN 是資料工程師最值得投資的工具之一。一旦學會讀查詢計畫,你就從「把 SQL 寫出來」升級成「把 SQL 寫快」。我們今天的範例雖然只是 100 萬列,但已能清楚展示索引與多執行緒的影響。在大資料場景(億級列),正確的索引策略可以把查詢從數小時降到秒級,這就是資料工程的價值。請把今天的 EXPLAIN 練習保留在工作目錄裡,未來你的管線遇到慢查詢時,這個工具會是你的第一個反應。

明天,我們會進入工具區塊:「DuckDB 入門:單機分析神器」。我們會把 de-journey/ 工作目錄裡 warehouse/ 的 DuckDB 檔案建起來,從 CLI 到 Python 介面、從讀 CSV 到 Parquet、從純 SQL 到與 pandas/Polars 的互通,全面認識這個工具。Day 8 之後 Day 9 會談 Parquet、Day 10 談 pandas、Day 11 談 Polars,整個工具區塊的設計是讓你在每個步驟都能切換到最適合的工具。

延伸資源

  • DuckDB EXPLAIN 與查詢計畫:https://duckdb.org/docs/guides/meta/explain.html。本篇 EXPLAIN ANALYZE 與 FORMAT 用法以這份為主。
  • DuckDB 索引文件:https://duckdb.org/docs/sql/indexes.html。CREATE INDEX、DROP INDEX、ART 結構的官方說明。
  • PostgreSQL 官方索引文件:https://www.postgresql.org/docs/current/indexes.html。資料倉儲 OLTP 索引的權威來源。
  • Use The Index, Luke! 索引教學:https://use-the-index-luke.com/。Apress 出版的索引權威指南。
  • DuckDB CLI:duckdb warehouse/de-journey.duckdb 開啟交談模式,可以用 .timer on 顯示查詢耗時。

留言

這個網誌中的熱門文章

Day 2 變數與資料型別

Day 2 變數與資料型別 引言 寫程式的過程中,變數與資料型別是處理資料的基礎。變數是存放資料的容器,資料型別則決定這筆資料有哪些特性、可以進行哪些操作。學會定義變數、認識各種資料型別,是學好 Python 的關鍵一步。 這篇文章會帶你了解 Python 中變數的觀念、如何定義變數,以及常見的資料型別,包括整數、浮點數、字串、布林值,還有串列、元組、字典與集合等容器型別。我們也會介紹變數的命名規則與撰寫風格建議,以及如何用 type() 檢查資料型別。 什麼是變數?如何在 Python 中定義變數 變數是在程式執行時用來存放資料的名稱。透過定義變數,我們可以給一筆資料一個名字,並在程式的其他地方用這個名字取用該筆資料。在 Python 中,變數不需要事先宣告型別,因為 Python 是動態型別語言,變數的型別由指定給它的值決定。 定義變數的基本語法 在 Python 中定義變數非常簡單,只要用賦值符號 = 把值指定給變數即可。例如: x = 5 # 定義變數 x,並把整數 5 賦值給它 name = "Alice" # 定義變數 name,並把字串 "Alice" 賦值給它 在這裡,x 是一個變數,被賦予整數 5;name 是另一個變數,被賦予字串 "Alice"。 變數的更新與覆寫 變數的值可以修改,也就是說,我們可以在程式的不同地方給同一個變數新的值。例如: x = 10 # x 最初被賦予 10 x = 15 # x 的值現在被更新為 15 這樣就能依照需求,在程式執行過程中靈活調整變數的值。 Python 的動態型別系統 Python 和某些靜態型別語言不同,定義變數時不需要宣告型別。賦值時,Python 會根據值自動判斷變數的型別。例如: x = 5 # x 是整數 x = 3.14 # x 變成浮點數 x = "Hi" # x 變成字串 同一個變數在程式執行過程中可以存放不同型別的值,這是 Python 的彈性之一。 常見資料型別 在 Python 中,資料型別決定我們可以對變數進行哪些操作...

Day 1 Python 簡介與環境設定

Day 1 Python 簡介與環境設定 引言 在現在的科技環境裡,程式設計已經是一項重要技能。無論你是對資料科學有興趣、想成為開發者,或是想踏入人工智慧(AI)領域,學會寫程式都能明顯提升你的競爭力。在眾多程式語言中,Python 因為語法簡單、功能強大、應用範圍廣泛,成為許多人進入程式世界的第一選擇。這篇文章會帶你認識 Python 的背景與優勢,並一步步教你在不同系統上安裝與設定 Python 開發環境,最後寫出第一支 Python 程式。 為什麼選擇 Python? Python 是一種高階程式語言,由 Guido van Rossum 在 1991 年發布。Python 的設計哲學強調程式碼的可讀性,並用縮排來定義程式區塊,這點和許多使用大括號的語言不同。簡潔的語法讓它成為初學者的理想選擇;就算是經驗豐富的開發者,也能用它完成複雜的專案。 Python 的優勢如下: 簡單易學 :Python 的語法清楚、結構簡潔,初學者很快就能上手。和其他語言相比,學習曲線相對平緩,不需要先弄懂一堆複雜觀念,就能開始寫程式。 應用範圍廣泛 :從資料科學、網頁開發、人工智慧、機器學習、自動化測試到網路爬蟲,Python 都有大量開源函式庫與工具支援,而且在這些領域都扮演關鍵角色。 豐富的函式庫與框架 :Python 的函式庫生態系非常龐大。做資料分析有 NumPy、Pandas;開發網站有 Django、Flask;做深度學習有 TensorFlow、PyTorch。各種需求幾乎都能找到對應的套件,讓開發更有效率。 跨平台支援 :Python 支援 Windows、macOS、Linux 等作業系統,程式通常不需要太多修改就能跨平台執行,讓開發與部署更有彈性。 活躍的社群 :Python 擁有龐大的開發者社群。學習或開發上遇到問題,幾乎都能在社群與論壇(例如 Stack Overflow)找到答案,對初學者來說是很強的後盾,也能減少卡關時的挫折感。 Python 的應用領域 Python 的流行與強大功能,讓許多領域都開始大量使用它。以下是幾個常見的應用方向: 資料科學 :隨著大數據與人工智慧興起,資料科學大量使用 Python。NumPy、Pandas 與 Matplotlib 等工具能處理和分析龐...

Python 從入門到 PyTorch 深度學習:開啟 AI 世界的大門

Python 從入門到 PyTorch 深度學習:開啟 AI 世界的大門 隨著人工智慧(AI)與深度學習(Deep Learning)快速發展,越來越多人對這些技術產生興趣。不論你是想踏入 AI 領域的初學者,還是已經有程式基礎的開發者,學好 Python 與深度學習框架(例如 PyTorch),都能為你打開更多可能。 為什麼選擇 Python? Python 已經是資料科學與人工智慧領域的首選語言。它的語法簡潔、容易上手,而且擁有龐大的生態系與大量開源函式庫。無論是資料處理、資料視覺化,還是建立機器學習與深度學習模型,Python 都能勝任。對想進入 AI 或資料科學領域的人來說,它幾乎是必備工具。 PyTorch 是什麼? PyTorch 是由 Meta(原 Facebook)AI 研究團隊開發的開源深度學習框架,以易用、靈活和動態計算圖著稱,是許多 AI 研究人員與開發者的首選。相較於其他框架,PyTorch 的寫法更貼近原生 Python,對初學者相對友善。無論是簡單的實驗,還是複雜的深度學習模型,PyTorch 都能提供強大的支援。 這個系列能帶給你什麼? 這個系列會從 Python 的基礎開始,帶你一步一步學習,最後能自己用 PyTorch 建立深度學習模型。即使你完全沒有寫過程式,也能跟著文章的節奏累積技能,理解 AI 與深度學習的核心觀念。 本系列涵蓋的主題 Python 基礎:從變數、條件判斷到函式與模組。 資料處理工具:用 NumPy 與 Pandas 有效率地操作資料。 資料視覺化:用 Matplotlib 與 Seaborn 把資料畫成圖表。 深度學習的數學基礎:線性代數、微積分與機率。 PyTorch 入門:理解張量、模型建構與 GPU 加速。 基礎深度學習模型:CNN 與 RNN 的實作應用。 深度學習專案實戰:從資料前處理到模型部署的端到端流程。 誰適合這個系列? 程式初學者 :如果你對 AI 充滿好奇,卻還沒寫過程式,系列的第一部分會帶你快速上手 Python,並幫助你理解深度學習的基本觀念。 資料科學愛好者 :如果你已經熟悉一些資料處理方法,進階部分會教你如何用 PyTorch 建構深度學習模型。 開發者與研究人員 :想更深入了...