一、为什么需要相似度计算
在图像搜索系统中,我们将图像转换为特征向量后,需要计算两个向量之间的相似程度。选择合适的相似度度量方法直接影响搜索结果的准确性和效率。
二、三大核心算法
1. 余弦相似度(Cosine Similarity)
核心思想:计算两个向量之间的夹角余弦值,关注方向而非大小。
数学公式:
cos(θ) = (A · B) / (||A|| × ||B||)
范围:[-1, 1],值越大越相似
Python实现:
import numpy as np
def cosine_similarity(vec1, vec2):
dot_product = np.dot(vec1, vec2)
norm1 = np.linalg.norm(vec1)
norm2 = np.linalg.norm(vec2)
return dot_product / (norm1 * norm2)
# 示例
a = np.array([1, 2, 3])
b = np.array([2, 4, 6])
print(f"余弦相似度: {cosine_similarity(a, b):.4f}") # 输出: 1.0000
优点:
- 对向量长度不敏感,适合不同尺度的数据
- 计算效率高,适合高维向量
- 在文本和图像检索中表现优异
- 结果直观,易于理解
缺点:
- 只关注方向,忽略向量大小
- 对零向量敏感
- 不满足三角不等式
适用场景:
- 图像特征向量比对(CLIP、ResNet特征)
- 文本相似度计算
- 推荐系统
2. 欧氏距离(Euclidean Distance)
核心思想:计算两点在空间中的直线距离,最直观的距离度量。
数学公式:
d = √(Σ(ai - bi)²)
范围:[0, +∞),值越小越相似
Python实现:
import numpy as np
def euclidean_distance(vec1, vec2):
return np.sqrt(np.sum((vec1 - vec2) ** 2))
# 或使用scipy
from scipy.spatial.distance import euclidean
distance = euclidean(vec1, vec2)
# 示例
a = np.array([1, 2, 3])
b = np.array([4, 5, 6])
print(f"欧氏距离: {euclidean_distance(a, b):.4f}") # 输出: 5.1962
优点:
- 直观易懂,符合人类空间认知
- 满足距离度量的所有性质
- 在低维空间表现优异
- 广泛应用于聚类算法(K-means)
缺点:
- 对向量长度敏感,需要归一化
- 高维空间中存在"维度灾难"
- 计算量较大(需要平方和开方)
- 对异常值敏感
适用场景:
- 低维特征空间(如颜色直方图)
- K-means聚类
- KNN分类
- 需要精确距离的场景
3. 曼哈顿距离(Manhattan Distance)
核心思想:计算两点在各维度上差值的绝对值之和,类似城市街区距离。
数学公式:
d = Σ|ai - bi|
范围:[0, +∞),值越小越相似
Python实现:
import numpy as np
def manhattan_distance(vec1, vec2):
return np.sum(np.abs(vec1 - vec2))
# 或使用scipy
from scipy.spatial.distance import cityblock
distance = cityblock(vec1, vec2)
# 示例
a = np.array([1, 2, 3])
b = np.array([4, 5, 6])
print(f"曼哈顿距离: {manhattan_distance(a, b):.4f}") # 输出: 9.0000
优点:
- 计算速度快,无需平方和开方
- 对异常值不敏感
- 在高维空间表现稳定
- 适合稀疏向量
缺点:
- 不考虑对角线距离
- 在某些场景下不够精确
- 对坐标系旋转敏感
适用场景:
- 高维稀疏向量(如词袋模型)
- 网格路径规划
- 对计算速度要求高的场景
- 异常值较多的数据
三、算法对比
| 算法 | 计算复杂度 | 归一化需求 | 高维表现 | 异常值敏感度 |
|---|---|---|---|---|
| 余弦相似度 | O(n) | 不需要 | 优秀 | 低 |
| 欧氏距离 | O(n) | 需要 | 一般 | 高 |
| 曼哈顿距离 | O(n) | 需要 | 良好 | 中 |
四、实战案例对比
使用三种算法对同一组图像特征进行相似度计算:
import numpy as np
from scipy.spatial.distance import cosine, euclidean, cityblock
# 模拟CLIP特征向量(512维)
query = np.random.randn(512)
image1 = query + np.random.randn(512) * 0.1 # 相似图像
image2 = np.random.randn(512) # 不相似图像
# 余弦相似度(值越大越相似)
cos_sim1 = 1 - cosine(query, image1)
cos_sim2 = 1 - cosine(query, image2)
print(f"余弦相似度 - 图像1: {cos_sim1:.4f}, 图像2: {cos_sim2:.4f}")
# 欧氏距离(值越小越相似)
euc_dist1 = euclidean(query, image1)
euc_dist2 = euclidean(query, image2)
print(f"欧氏距离 - 图像1: {euc_dist1:.4f}, 图像2: {euc_dist2:.4f}")
# 曼哈顿距离(值越小越相似)
man_dist1 = cityblock(query, image1)
man_dist2 = cityblock(query, image2)
print(f"曼哈顿距离 - 图像1: {man_dist1:.4f}, 图像2: {man_dist2:.4f}")
五、如何选择
场景1:深度学习特征向量(CLIP、ResNet)
推荐:余弦相似度
- 深度学习特征已经过归一化
- 关注特征方向比大小更重要
- 计算效率高
- 业界标准做法
场景2:低维特征(颜色、纹理)
推荐:欧氏距离
- 维度较低,不存在维度灾难
- 特征值有明确物理意义
- 需要精确距离度量
场景3:稀疏高维向量
推荐:曼哈顿距离
- 计算速度快
- 对稀疏性友好
- 异常值影响小
场景4:实时搜索系统
推荐:余弦相似度 + FAISS优化
- FAISS对余弦相似度有专门优化
- 支持GPU加速
- 可使用近似算法(IVF、HNSW)
六、性能优化技巧
1. 向量归一化
# 归一化后,余弦相似度 = 点积
normalized_vec = vec / np.linalg.norm(vec)
similarity = np.dot(normalized_vec1, normalized_vec2)
2. 批量计算
# 使用矩阵运算加速
from sklearn.metrics.pairwise import cosine_similarity
similarities = cosine_similarity(query_vectors, database_vectors)
3. FAISS加速
import faiss
# 使用内积搜索(等价于余弦相似度,需先归一化)
index = faiss.IndexFlatIP(dimension)
index.add(normalized_vectors)
distances, indices = index.search(query, k=10)
七、总结
三种相似度算法各有优劣:
- 余弦相似度:图像搜索首选,适合深度学习特征
- 欧氏距离:低维精确场景,需要归一化
- 曼哈顿距离:高维稀疏数据,计算速度快
我们的系统采用余弦相似度结合FAISS优化,在百万级图片库中实现毫秒级搜索。立即体验