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 道题,覆盖分词、词表、计数、余弦相似度和词汇匹配局限。
继续阅读: