
什么是SQL注入?理解、風險與防范技巧
cosine_sim(A, B) = (A · B) / (||A|| * ||B||)
如果你先將向量A和B歸一化為單位向量A’和B’,再計算它們的點積,那么:
A' · B' = cosine_sim(A, B)
此時,點積(Dot Product)就等于余弦相似度。
核心影響:歸一化讓相似度計算從關注"方向和強度"變成了只關注"純方向"。
不歸一化 | 歸一化后 | |
---|---|---|
關注點 | 方向 + 強度(總量) | 純方向(比例、形態) |
模長作用 | 是關鍵因素,模長大的向量占主導 | 被完全消除,所有向量平等 |
好比 | 比較兩首歌的絕對音量和音色 | 只比較兩首歌的音色,把音量調成一致 |
理解了這一點,我們就可以通過案例和代碼來看看如何選擇了。
場景:文檔相似度計算
假設你有兩篇文檔:
我們用TF-IDF模型將它們轉化為向量。
import numpy as np
from sklearn.feature_extraction.text import TfidfVectorizer
# 示例文檔
documents = [
"machine learning deep neural network artificial intelligence", # 長文檔A
"ai artificial intelligence" # 短文檔B
]
# 計算TF-IDF向量
vectorizer = TfidfVectorizer()
tfidf_matrix = vectorizer.fit_transform(documents)
# 轉換為稠密數組以便演示
vectors = tfidf_matrix.toarray()
print("原始TF-IDF向量:")
print("文檔A:", vectors[0])
print("文檔B:", vectors[1])
print()
# 計算模長
norm_a = np.linalg.norm(vectors[0])
norm_b = np.linalg.norm(vectors[1])
print(f"文檔A模長: {norm_a:.4f}")
print(f"文檔B模長: {norm_b:.4f}")
print()
# 計算未歸一化的余弦相似度
dot_product = np.dot(vectors[0], vectors[1])
cosine_sim_unnormalized = dot_product / (norm_a * norm_b)
print(f"未歸一化余弦相似度: {cosine_sim_unnormalized:.4f}")
# 歸一化向量
normalized_a = vectors[0] / norm_a
normalized_b = vectors[1] / norm_b
print("\n歸一化后的向量:")
print("文檔A:", normalized_a)
print("文檔B:", normalized_b)
print()
# 計算歸一化后的余弦相似度(即點積)
cosine_sim_normalized = np.dot(normalized_a, normalized_b)
print(f"歸一化后余弦相似度: {cosine_sim_normalized:.4f}")
# 驗證:直接使用余弦相似度公式應與歸一化后點積結果一致
cosine_sim_direct = dot_product / (np.linalg.norm(vectors[0]) * np.linalg.norm(vectors[1]))
print(f"直接計算余弦相似度: {cosine_sim_direct:.4f}")
不歸一化的問題:
長文檔A的向量模長(A
)會遠大于短文檔B的模長(B
)。即使兩篇文章主題高度相關,由于分母A|| * ||B
巨大,計算出的余弦相似度值也會被拉低。這顯然不合理——我們不能因為一篇文章寫得長,就說它和短文不相似。
歸一化的效果:
歸一化將兩個向量的模長統一為1。這時,相似度計算只關心詞頻分布的相對比例。既然兩篇文章都重點討論了"人工智能"、"機器學習"等話題(即向量方向相近),那么它們的相似度就會很高,完美忽略了文檔長度的干擾。
? 結論:在文本處理、圖像顏色直方圖比較、用戶興趣偏好分析等場景中,模長(文檔長度、圖片亮度、用戶活躍度)是干擾噪聲,我們真正關心的是分布形態。此時,必須歸一化。
場景:推薦系統中的用戶評分預測
假設在一個電影評分系統(1-5分)中,有兩個用戶:
# 用戶評分向量示例
user_a_ratings = np.array([5, 3, 2, 1]) # 苛刻用戶,但對某部電影給出5星
user_b_ratings = np.array([5, 4, 4, 5]) # 寬容用戶,對同一部電影也給出5星
print("用戶評分向量:")
print("用戶A(苛刻):", user_a_ratings)
print("用戶B(寬容):", user_b_ratings)
print()
# 計算模長
norm_a = np.linalg.norm(user_a_ratings)
norm_b = np.linalg.norm(user_b_ratings)
print(f"用戶A評分向量模長: {norm_a:.4f}")
print(f"用戶B評分向量模長: {norm_b:.4f}")
print()
# 計算未歸一化的點積相似度
dot_product_unnormalized = np.dot(user_a_ratings, user_b_ratings)
print(f"未歸一化點積相似度: {dot_product_unnormalized:.4f}")
# 計算未歸一化的余弦相似度
cosine_sim_unnormalized = dot_product_unnormalized / (norm_a * norm_b)
print(f"未歸一化余弦相似度: {cosine_sim_unnormalized:.4f}")
print()
# 歸一化向量
normalized_a = user_a_ratings / norm_a
normalized_b = user_b_ratings / norm_b
print("歸一化后的評分向量:")
print("用戶A:", normalized_a)
print("用戶B:", normalized_b)
print()
# 計算歸一化后的點積相似度
dot_product_normalized = np.dot(normalized_a, normalized_b)
print(f"歸一化后點積相似度: {dot_product_normalized:.4f}")
# 分析差異
print("\n分析:")
print("歸一化前,用戶A的5星是絕對值5,用戶B的5星也是絕對值5")
print("歸一化后,用戶A的5星被縮放為{:.4f},用戶B的5星被縮放為{:.4f}".format(
normalized_a[0], normalized_b[0]))
print("這意味著用戶A的5星在其評分體系中占比更大,但失去了'絕對值5星'的語義")
歸一化的問題:
如果我們對用戶評分向量進行歸一化,會發生一件微妙的事情:用戶A的5星是他評分體系中的最高分,而用戶B的5星只是比他的平均分略高一點。歸一化拉平了他們的評分尺度,模糊了"他們都給出了絕對最高分"這一強烈信號。系統可能會錯誤地認為用戶A對《星際穿越》的喜愛程度遠超用戶B。
不歸一化的效果:
保持向量的原始模長,點積運算會同時考慮到評分方向和評分絕對值。兩個5星是絕對相等的,這表明他們對《星際穿越》的喜愛是同等強烈的。這對于基于用戶的協同過濾(User-CF)算法至關重要。
? 結論:在推薦系統、經濟數據對比、信號強度分析等場景中,向量的模長(絕對評分、經濟總量、信號振幅)是核心比較信息的一部分。此時,不應使用歸一化。
下次當你猶豫是否要歸一化時,問自己這三個問題:
模長差異是噪聲還是信息?
我想找"同類"還是"克隆"?
我的算法核心是什么?
最后的建議:
在沒有明確先驗知識時,先嘗試歸一化通常是更安全的選擇,因為它能消除很多量級帶來的偏差。但最好的方法永遠是基于你的業務目標,構建一個驗證集,同時試驗兩種方案,讓最終的模型效果告訴你答案。