專項練習 · 9 分鐘閱讀

Python 分詞練習:詞元、向量與餘弦相似度

分詞是 AI 流水線第一個可能悄悄丟失資訊的地方。這些練習把選擇擺到明面上:什麼算一個詞元、詞表如何共用,以及相似度分數能說明什麼、不能說明什麼。

先規範化,再切分

轉小寫是簡單的規範化步驟,讓 Python 和 python 成為同一個詞元,但也會抹去人名或程式碼中可能重要的資訊。選定規則後,訓練文字和新文字都要一致應用。

import re

def tokenize(text):
    return re.findall(r"[a-z']+", text.lower())

print(tokenize("Python, Python! Text."))

所有文件使用同一規則

共用一個詞表

從訓練文件構造詞表,而不是為每篇新文件單獨構造。對詞元排序,讓維度順序確定。詞表外的詞元需要明確策略,例如忽略或映射到未知詞元。

docs = ["python text", "text models"]
vocab = sorted({word for doc in docs for word in doc.split()})
index = {word: i for i, word in enumerate(vocab)}
print(index)

穩定的維度

把詞元計數成向量

計數向量很容易檢查:每個位置表示某個詞表詞元出現了幾次。如果長文件不應自動與所有內容更相似,可以按文件長度歸一化。

def vector(text, index):
    counts = [0] * len(index)
    for word in text.split():
        if word in index:
            counts[index[word]] += 1
    return counts

index = {"python": 0, "text": 1}
print(vector("python text text", index))

可以審計的向量

用餘弦比較

餘弦相似度衡量向量夾角,因此忽略長度。當一篇文件只是更長時,這很有用。但如果兩句話用詞完全不同,它仍然無法判斷語意是否相同。

import math

def cosine(a, b):
    dot = sum(x * y for x, y in zip(a, b))
    norm_a = math.sqrt(sum(x * x for x in a))
    norm_b = math.sqrt(sum(y * y for y in b))
    if not norm_a or not norm_b:
        return 0.0
    return dot / (norm_a * norm_b)

print(round(cosine([1, 1], [2, 2]), 3))
print(round(cosine([1, 0], [0, 1]), 3))

看方向,不看長度

知道詞袋漏掉了什麼

詞袋模型會給 「dog bites man」 和 「man bites dog」 相同向量。字元 n-gram 可以緩解拼字錯誤,詞嵌入可以捕捉分布相似性,但都不能代替在真實任務上的評估。

def bag(text):
    return sorted(text.lower().split())

print(bag("dog bites man") == bag("man bites dog"))

詞序消失了

你的練習清單

常見問題

詞元一定是一個單字嗎?

不一定。生產分詞器可能按標點、子詞、位元組或 Unicode 碼點切分。按單字只是教學上的簡化。

比較文字一定要用神經嵌入嗎?

不一定。詞袋和 TF-IDF 是許多搜尋與分類任務的強基線。詞彙重疊不夠時,嵌入才更有價值。

為什麼餘弦會回傳 0.0?

兩個向量沒有共用的非零維度,或者其中一個向量全為零。保護條件把這個情況顯式化。

練習詞元與向量

12 道題,覆蓋分詞、詞表、計數、餘弦相似度和詞彙比對局限。

打開練習

繼續閱讀: