相似性度量(距离度量)方法(二):实战应用与性能对比

1. 从理论到实战:为什么选对距离度量如此重要?

朋友们,上次我们聊了三十多种距离度量方法,从最熟悉的欧式距离到有点绕的相对熵,公式列了一大堆。我知道,很多人看完的感觉可能是:“懂了,但又没完全懂。这么多方法,我到底该用哪个?” 别急,这正是我们这篇要解决的核心问题。理论公式是地图,实战应用才是真正的旅程。选错了“距离”,你的机器学习模型可能就在原地打转,或者朝着完全错误的方向狂奔。

我举个真实的例子。几年前我做一个电商用户分群的项目,目标是根据用户的浏览时长、点击次数、购买金额等十几个行为特征,把用户分成不同的价值群体,方便进行精准营销。一开始,我想当然地用了欧式距离,觉得它最通用嘛。结果跑出来的聚类结果一塌糊涂,高消费用户和只是浏览时间长但从不买单的用户被分到了一起。为什么?因为购买金额这个特征的数值范围(比如几元到几万元)远大于点击次数(通常几十到几百次),在欧式距离的计算中,金额的微小差异就“淹没”了点击行为的显著差异。这就像用米和毫米一起量身高而不做任何处理,结果肯定失真。

这就是距离度量的选择直接决定模型成败的典型案例。它不仅仅是数学计算,更是你对数据本质理解的体现。不同的度量方法,实际上是在用不同的“尺子”和“视角”来衡量数据点之间的远近。有些尺子对数值大小敏感(如欧式距离),有些只关心方向(如余弦相似度),有些能自动处理量纲和相关性(如马氏距离),还有些专门为特定类型的数据而生(如编辑距离之于文本)。

所以,今天的文章,我们不罗列公式,而是聚焦于“用”。我会结合我在推荐系统、图像检索、自然语言处理等多个领域的实战经验,带大家分析几种最核心的距离度量方法在实际场景中的表现,用代码对比它们的计算效率,并给出清晰的选择指南。我们的目标很明确:看完之后,你能像老司机选工具一样,面对你的数据,立刻知道该抄起哪把“尺子”。

2. 五大核心距离度量实战场景深度剖析

理论说再多,不如一行代码、一个案例来得实在。下面我们就深入几个最常见的机器学习场景,看看不同的距离度量是如何各显神通的。

2.1 场景一:数值型数据聚类(以K-Means为例)

K-Means是聚类分析的招牌算法,它的核心就是不断计算数据点到簇中心的距离并重新归类。这里距离的选择直接决定了簇的形状。

欧式距离 vs 曼哈顿距离 vs 余弦相似度

假设我们有一组二维数据,特征分别是“每周登录天数”和“每周消费金额”。我们分别用这三种距离跑一下K-Means,看看效果。

import numpy as np
import matplotlib.pyplot as plt
from sklearn.cluster import KMeans
from sklearn.preprocessing import StandardScaler

# 生成模拟数据:两类用户,一类活跃低消费,一类活跃高消费
np.random.seed(42)
class1 = np.random.randn(100, 2) * np.array([0.5, 10]) + np.array([2, 50])  # 低活跃,中高消费
class2 = np.random.randn(100, 2) * np.array([3, 5]) + np.array([10, 20])   # 高活跃,中低消费
X = np.vstack([class1, class2])

# 数据标准化(非常重要,尤其是对欧式距离)
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)

# 使用不同的距离度量(通过自定义函数+循环模拟,实际中KMeans默认欧式距离,其他距离需用其他算法如K-Medoids)
# 为了演示,我们使用欧式距离和余弦距离(通过归一化后,余弦距离与欧式距离在某些情况下关联)
from sklearn.metrics.pairwise import cosine_distances, euclidean_distances

# 计算距离矩阵以供分析
print("欧式距离矩阵前5x5:")
print(euclidean_distances(X_scaled[:5, :]))
print("\n余弦距离矩阵前5x5:")
print(cosine_distances(X_scaled[:5, :]))

# 使用KMeans(基于欧式距离)
kmeans_euclidean = KMeans(n_clusters=2, random_state=42).fit(X_scaled)
labels_eu = kmeans_euclidean.labels_

# 为了对比,我们手动实现一个基于余弦距离的“粗糙”划分(仅用于演示思想)
# 实际上,scikit-learn的KMeans不支持余弦距离,这里用归一化后的数据,其欧式距离与余弦距离存在单调关系
X_normalized = X_scaled / np.linalg.norm(X_scaled, axis=1, keepdims=True) # L2归一化,此时两点欧式距离平方 = 2*(1 - 余弦相似度)
kmeans_cosine_sim = KMeans(n_clusters=2, random_state=42).fit(X_normalized)
labels_cos = kmeans_cosine_sim.labels_

# 可视化
fig, axes = plt.subplots(1, 3, figsize=(15, 4))
axes[0].scatter(X[:, 0], X[:, 1], c='gray', alpha=0.6, label='原始数据')
axes[0].set_title('原始数据分布')
axes[0].set_xlabel('登录天数(模拟)')
axes[0].set_ylabel('消费金额(模拟)')

axes[1].scatter(X_scaled[:, 0], X_scaled[:, 1], c=labels_eu, cmap='viridis', alpha=0.6)
axes[1].scatter(kmeans_euclidean.cluster_centers_[:, 0], kmeans_euclidean.cluster_centers_[:, 1], 
                marker='*', s=300, c='red', label='簇中心')
axes[1].set_title('基于(标准化后)欧式距离的K-Means聚类')
axes[1].set_xlabel('特征1(标准化后)')
axes[1].set_ylabel('特征2(标准化后)')

axes[2].scatter(X_scaled[:, 0], X_scaled[:, 1], c=labels_cos, cmap='plasma', alpha=0.6)
axes[2].scatter(kmeans_cosine_sim.cluster_centers_[:, 0], kmeans_cosine_sim.cluster_centers_[:, 1], 
                marker='*', s=300, c='red', label='簇中心')
axes[2].set_title('基于(归一化后)余弦相似思想的划分')
axes[2].set_xlabel('特征1(标准化后)')
axes[2].set_ylabel('特征2(标准化后)')

plt.tight_layout()
plt.show()

实战解读与选择建议:

跑完这段代码,你可能会发现两种方法结果差异很大。欧式距离形成的簇边界通常是“圆形”或“超球体”,它追求的是绝对数值空间的最近邻。在数据经过标准化后,它平等看待每一个维度。而余弦相似度(或距离)关心的是向量之间的夹角,即数据点的“方向”。在上面的例子里,如果我们不关心用户具体花了多少钱,只关心“高活跃高消费”这个模式与“低活跃低消费”模式的差异,余弦相似度可能更合适。例如,用户A(登录5天,消费500元)和用户B(登录10天,消费1000元)在欧式距离上很远,但在余弦相似度上几乎一致(方向相同)。

曼哈顿距离则产生轴对齐的矩形簇边界,对异常值比欧式距离更不敏感。如果你的数据有很多离群点,或者特征具有可加性(比如城市网格距离),曼哈顿距离是更好的选择。

给你的 checklist:

  • 数据已标准化,且你认为所有特征同等重要,簇期望呈球形 → 欧式距离
  • 数据有异常值,或特征具有网格状结构 → 曼哈顿距离
  • 你想忽略向量的模长,只比较模式或趋势(如文本的词频向量)→ 余弦相似度

2.2 场景二:图像检索与相似度匹配

以图搜图是距离度量的经典舞台。这里,一张图片通常被表示为一个高维特征向量(例如,通过CNN卷积神经网络提取的4096维特征)。我们需要在这个高维空间中快速找到与查询图片最“近”的图片。

欧式距离 vs 余弦相似度 vs 杰卡德距离(用于二进制特征)

假设我们已经有了图片的特征向量库。对于真实的全尺寸特征,计算效率至关重要。

import numpy as np
import time

# 模拟一个图像特征数据库:10000张图片,每张图片4096维特征
np.random.seed(0)
num_images = 10000
dim = 4096
database_features = np.random.randn(num_images, dim).astype(np.float32) # 使用float32加速
# 模拟查询图片
query_feature = np.random.randn(dim).astype(np.float32)

# 1. 欧式距离 (L2) 暴力搜索
def l2_search(query, database):
    distances = np.linalg.norm(database - query, axis=1)
    return np.argsort(distances)[:5]  # 返回最相似的5张图片索引

# 2. 余弦相似度搜索
def cosine_search(query, database):
    # 归一化
    query_norm = query / np.linalg.norm(query)
    db_norm = database / np.linalg.norm(database, axis=1, keepdims=True)
    similarities = np.dot(db_norm, query_norm)  # 点积即余弦相似度
    return np.argsort(similarities)[-5:][::-1]  # 相似度从高到低

# 3. 二进制特征情况下的杰卡德距离(模拟:将特征二值化)
# 假设我们使用局部二值模式(LBP)等特征,生成二进制向量
database_binary = (database_features > 0).astype(np.uint8)
query_binary = (query_feature > 0).astype(np.uint8)

def jaccard_search(query_bin, database_bin):
    # 杰卡德相似度 = 交集 / 并集
    # 利用向量化操作避免慢循环
    intersection = np.logical_and(database_bin, query_bin).sum(axis=1)
    union = np.logical_or(database_bin, query_bin).sum(axis=1)
    jaccard_sim = intersection / (union + 1e-8)  # 防止除零
    return np.argsort(jaccard_sim)[-5:][::-1]

# 性能与结果对比
print("=== 性能对比 (暴力搜索) ===")
start = time.time()
l2_top5 = l2_search(query_feature, database_features)
print(f"欧式距离搜索耗时: {time.time() - start:.4f} 秒, Top5索引: {l2_top5}")

start = time.time()
cos_top5 = cosine_search(query_feature, database_features)
print(f"余弦相似度搜索耗时: {time.time() - start:.4f} 秒, Top5索引: {cos_top5}")

start = time.time()
jac_top5 = jaccard_search(query_binary, database_binary)
print(f"杰卡德相似度搜索耗时: {time.time() - start:.4f} 秒, Top5索引: {jac_top5}")

# 验证不同度量结果的一致性(通常不一致)
print("\n=== 不同度量方法Top1结果对比 ===")
print(f"欧式距离Top1的图片索引: {l2_top5[0]}")
print(f"余弦相似度Top1的图片索引: {cos_top5[0]}")
print(f"杰卡德相似度Top1的图片索引: {jac_top5[0]}")

实战解读与选择建议:

在图像检索中,余弦相似度往往是首选,尤其是在使用深度学习特征时。因为这些特征向量的模长(即特征的“强度”)可能受到光照、对比度等无关因素的影响,而向量方向更能代表图像的语义内容。从性能上看,由于都需要计算全量距离,三者耗时在一个量级,但余弦相似度在计算前通常需要归一化,多了一步。

杰卡德距离主要用于比较二进制特征,比如从图像中提取的局部关键点描述符的二值化版本,或者简单的颜色直方图二值化。它的计算速度很快,尤其适合硬件加速。

关键陷阱提醒:在高维空间中(比如4096维),所有数据点之间的距离会变得非常均匀,这就是所谓的“维数灾难”。在这种情况下,距离度量之间的差异可能会被放大,甚至失效。一个常见的做法是先用PCAt-SNE进行降维,再在低维空间计算距离,效果和效率都会更好。

2.3 场景三:文本分析与自然语言处理

文本数据天生就是高维稀疏的(一个词表可能上万维,但一篇文章只包含几十个词)。处理这类数据,距离度量的选择尤为讲究。

余弦相似度 vs 杰卡德相似度 vs 编辑距离

我们分别用这三种方法来计算两段文本的相似性。

from sklearn.feature_extraction.text import CountVectorizer, TfidfVectorizer
import Levenshtein  # 需要安装:pip install python-Levenshtein

documents = [
    "机器学习是人工智能的核心领域",
    "深度学习推动人工智能快速发展",
    "今天天气很好,我们出去散步吧",
    "人工智能和机器学习密切相关"
]

# 方法1:基于词袋模型的余弦相似度(TF-IDF加权)
vectorizer_tfidf = TfidfVectorizer()
tfidf_matrix = vectorizer_tfidf.fit_transform(documents)
print("TF-IDF特征维度:", tfidf_matrix.shape)

from sklearn.metrics.pairwise import cosine_similarity
cos_sim_matrix = cosine_similarity(tfidf_matrix, tfidf_matrix)
print("\n基于TF-IDF的余弦相似度矩阵:")
print(cos_sim_matrix)

# 方法2:基于集合的杰卡德相似度(以词语为元素)
vectorizer_binary = CountVectorizer(binary=True)  # 二进制出现与否
binary_matrix = vectorizer_binary.fit_transform(documents).toarray()

def jaccard_sim(set1_idx, set2_idx, matrix):
    set1 = set(np.where(matrix[set1_idx] > 0)[0]) # 获取非零特征索引作为词集合
    set2 = set(np.where(matrix[set2_idx] > 0)[0])
    intersection = len(set1.intersection(set2))
    union = len(set1.union(set2))
    return intersection / union if union > 0 else 0

print(f"\n文档0与文档1的杰卡德相似度(基于词集): {jaccard_sim(0, 1, binary_matrix):.3f}")
print(f"文档0与文档2的杰卡德相似度(基于词集): {jaccard_sim(0, 2, binary_matrix):.3f}")

# 方法3:编辑距离(Levenshtein Distance)
print("\n=== 编辑距离(字符级别)===")
doc0, doc1 = documents[0], documents[1]
lev_dist = Levenshtein.distance(doc0, doc1)
# 编辑距离相似度可以归一化,例如:1 - (编辑距离 / 最大长度)
max_len = max(len(doc0), len(doc1))
lev_sim = 1 - (lev_dist / max_len) if max_len > 0 else 1
print(f"文档0: '{doc0}'")
print(f"文档1: '{doc1}'")
print(f"编辑距离: {lev_dist}, 归一化相似度: {lev_sim:.3f}")

实战解读与选择建议:

对于长文档、段落级的语义相似度比较,基于TF-IDF的余弦相似度是行业标准。它既考虑了词频(TF),又降低了常见词的权重(IDF),能较好地捕捉文档的主题信息。你从上面的矩阵可以看到,文档0和文档3(都包含“机器学习”和“人工智能”)相似度最高。

杰卡德相似度更适用于短文本、标签集合或关键词列表的快速比较。它计算简单,但完全忽略了词序和词频。比如,“猫追老鼠”和“老鼠追猫”的杰卡德相似度是100%,这显然不符合语义。

编辑距离则是处理字符串、序列比对(如DNA序列、拼写纠错)的利器。在NLP中,它常用于词干提取、模糊匹配等任务。但对于整个文档,编辑距离计算代价高昂且对词序过于敏感,一般不用于直接衡量文档相似性。

给你的快速决策流:

  • 比较文章、段落、句子语义 → TF-IDF + 余弦相似度(或直接使用Sentence-BERT等嵌入模型)
  • 比较标签、关键词、分类集合 → 杰卡德相似度
  • 拼写检查、序列对齐、短字符串模糊匹配 → 编辑距离

2.4 场景四:概率分布比较与模型评估

在生成模型(如GAN)、语言模型或任何涉及概率预测的场景中,我们需要衡量模型预测的分布与真实分布有多接近。这里,传统的几何距离不再适用,我们需要信息论中的“距离”。

KL散度 vs JS散度 vs 交叉熵

假设我们有一个三分类问题的真实标签分布和两个不同模型的预测分布。

import numpy as np
from scipy.special import kl_div, rel_entr

# 真实分布(one-hot编码,实际中可能是软标签)
P_true = np.array([1.0, 0.0, 0.0])  # 真实类别为0
# 模型A的预测(比较自信且正确)
Q_modelA = np.array([0.9, 0.05, 0.05])
# 模型B的预测(不自信且有点错误)
Q_modelB = np.array([0.6, 0.3, 0.1])

# 1. KL散度 (Kullback-Leibler Divergence) - 非对称
def kl_divergence(p, q):
    # 使用scipy的rel_entr,它处理了0*log(0)的情况
    return np.sum(rel_entr(p, q))

kl_a = kl_divergence(P_true, Q_modelA)
kl_b = kl_divergence(P_true, Q_modelB)
print(f"KL散度 - 模型A: {kl_a:.4f}")
print(f"KL散度 - 模型B: {kl_b:.4f}")
print(f"-> KL散度越小越好,模型A更优。\n")

# 2. JS散度 (Jensen-Shannon Divergence) - 对称,平滑
def js_divergence(p, q):
    m = 0.5 * (p + q)
    return 0.5 * kl_divergence(p, m) + 0.5 * kl_divergence(q, m)

js_a = js_divergence(P_true, Q_modelA)
js_b = js_divergence(P_true, Q_modelB)
print(f"JS散度 - 模型A: {js_a:.4f}")
print(f"JS散度 - 模型B: {js_b:.4f}")
print(f"-> JS散度同样越小越好,模型A更优。\n")

# 3. 交叉熵 (Cross-Entropy) - 常用作损失函数
def cross_entropy(p, q, eps=1e-12):
    q = np.clip(q, eps, 1. - eps)  # 防止log(0)
    return -np.sum(p * np.log(q))

ce_a = cross_entropy(P_true, Q_modelA)
ce_b = cross_entropy(P_true, Q_modelB)
print(f"交叉熵 - 模型A: {ce_a:.4f}")
print(f"交叉熵 - 模型B: {ce_b:.4f}")
print(f"-> 交叉熵越小越好,模型A更优。")

# 关系验证:交叉熵 = 熵 + KL散度。这里真实分布P的熵为0(确定事件),所以交叉熵 = KL散度
print(f"\n验证: KL散度A ({kl_a:.4f}) 应约等于 交叉熵A ({ce_a:.4f})")

实战解读与选择建议:

KL散度衡量的是用分布Q来近似分布P时损失的信息量。它非对称,即KL(P||Q) != KL(Q||P)。在机器学习中,我们通常固定P为真实分布,最小化KL(P||Q),这等价于最小化交叉熵。因为对于固定的P,其熵是常数。所以,在训练分类神经网络时,你用的CrossEntropyLoss本质上就是在最小化KL散度。

JS散度是KL散度的对称平滑版本,取值范围在[0,1]之间。它在GAN最初的理论中被用来衡量生成分布和真实分布的差异,但因为训练中容易梯度消失,后来被Wasserstein距离等取代。不过,JS散度仍然是评估两个分布相似性的一个好工具,尤其当你想得到一个对称的、有界的度量时。

给你的使用指南:

  • 训练分类模型,计算损失 → 交叉熵损失(等价于最小化KL散度)
  • 需要对称地比较两个未知分布(如评估两个生成模型)→ JS散度
  • 需要严格衡量一个分布近似另一个分布的信息损失,且方向重要 → KL散度

3. 性能大比拼:计算效率与优化技巧

聊完了场景,我们来点硬核的。在实际工程中,尤其是面对海量数据时,距离度量的计算效率可能成为瓶颈。我们来实测一下几种常见距离的计算速度,并分享几个我踩过坑才学到的优化技巧。

import numpy as np
import time
from scipy.spatial.distance import cdist
from sklearn.metrics.pairwise import pairwise_distances

# 生成大规模数据
n_samples = 10000
n_features = 128
X = np.random.randn(n_samples, n_features).astype(np.float32)
Y = np.random.randn(1000, n_features).astype(np.float32)  # 另一组数据,用于计算跨组距离

print(f"数据规模: X {X.shape}, Y {Y.shape}")
print("="*50)

# 测试方法1:纯NumPy循环 (最慢,基线)
def naive_euclidean(X, Y):
    m, n = X.shape[0], Y.shape[0]
    dists = np.zeros((m, n))
    for i in range(m):
        for j in range(n):
            dists[i, j] = np.sqrt(np.sum((X[i] - Y[j]) ** 2))
    return dists

# 测试方法2:NumPy广播 (向量化)
def vectorized_euclidean(X, Y):
    # 利用 (a-b)^2 = a^2 + b^2 - 2ab
    X_sq = np.sum(X**2, axis=1, keepdims=True)
    Y_sq = np.sum(Y**2, axis=1, keepdims=True).T
    XY = np.dot(X, Y.T)
    dists = np.sqrt(X_sq + Y_sq - 2 * XY)
    # 防止数值误差导致负数
    np.maximum(dists, 0, out=dists)
    return dists

# 测试方法3:使用SciPy的cdist (高度优化)
# 测试方法4:使用scikit-learn的pairwise_distances

metrics_to_test = ['euclidean', 'cityblock'] # 'cityblock'即曼哈顿距离
implementations = {
    'SciPy cdist': lambda m: cdist(X, Y, metric=m),
    'sklearn pairwise': lambda m: pairwise_distances(X, Y, metric=m, n_jobs=-1), # 并行
    'NumPy 向量化': lambda m: vectorized_euclidean(X, Y) if m == 'euclidean' else None
}

for metric in metrics_to_test:
    print(f"\n度量方法: {metric}")
    for name, func in implementations.items():
        if metric != 'euclidean' and name == 'NumPy 向量化':
            continue # 只实现欧式
        try:
            start = time.time()
            _ = func(metric)
            elapsed = time.time() - start
            print(f"  {name:20} 耗时: {elapsed:.4f} 秒")
        except Exception as e:
            print(f"  {name:20} 失败: {e}")

实测结果分析与优化心法:

跑完这段代码,你会清晰地看到不同实现方式之间几个数量级的性能差异。永远不要自己写双重循环来计算大规模数据的距离矩阵,这是血的教训。NumPy的广播机制和BLAS优化库(背后是SciPy和scikit-learn)的速度优势是碾压性的。

核心优化技巧:

  1. 向量化是生命线:尽可能使用像np.linalg.norm(X - Y, axis=1)或上述的向量化公式,将循环交给高度优化的C/Fortran底层库。
  2. 善用专业库scipy.spatial.distance.cdistpdist,以及sklearn.metrics.pairwise_distances,是为你优化到极致的工具。它们支持多种度量,并且通常有并行计算选项(n_jobs=-1)。
  3. 降维打击:如果特征维度极高(比如>1000),先使用PCA随机投影特征选择降低维度,再计算距离。计算复杂度从O(n^2 * d)降到O(n^2 * k),其中k << d,速度提升立竿见影。
  4. 近似最近邻(ANN):当数据量巨大(n > 10万)时,精确计算所有两两距离已不可能。必须使用近似算法,如Faiss(Facebook)、Annoy(Spotify)、HNSW等。这些库用索引结构牺牲少量精度,换来百倍千倍的查询速度。在我的一个亿级图像检索项目里,HNSW把查询时间从小时级降到了毫秒级。
  5. 数据类型很重要:如果精度要求允许,使用float32甚至float16(GPU上)可以大幅减少内存占用和计算时间。上面的代码中我们特意指定了astype(np.float32)

4. 如何为你自己的项目选择最佳度量?

看了这么多对比,最后我们来梳理一个万能的选择思路。下次当你面对新数据集时,可以跟着这个流程图走(当然,是在脑子里):

第一步:审视你的数据

  • 数据类型是什么? 稠密数值向量、稀疏向量、二进制向量、概率分布、字符串、序列还是集合?
  • 量纲和尺度统一吗? 如果特征单位不同(米、秒、元),必须标准化(StandardScaler)或归一化(MinMaxScaler)。这是使用欧式、曼哈顿等距离的前提
  • 特征相关吗? 如果特征之间存在强相关性(比如身高和体重),马氏距离可能是更好的选择,因为它考虑了协方差结构。计算马氏距离需要求协方差矩阵的逆,当特征维度过高或样本不足时可能不稳定,这点要注意。

第二步:明确你的任务目标

  • 聚类:你想得到球状簇(欧式),还是轴对齐的簇(曼哈顿),还是基于密度的簇(通常用欧式配合DBSCAN)?对于K-Means,尝试不同的距离预计算并结合轮廓系数评估。
  • 分类/回归(KNN):K近邻算法同样依赖距离。可以把它作为超参数,用网格搜索来调优。
  • 相似性检索:关注绝对数值差异(欧式),还是模式相似(余弦),还是集合重叠(杰卡德)?
  • 分布比较:评估生成模型质量(JS散度、Wasserstein距离),或训练分类模型(交叉熵)。

第三步:考虑计算约束

  • 数据规模:小数据(<1万)可任性,大数据必须考虑近似算法或降维。
  • 线上延迟要求:毫秒级响应必须建立索引(如ANN库),批处理任务可以接受秒级。
  • 特征维度:高维数据优先考虑余弦相似度或先降维。

第四步:实验是唯一标准 理论再完美,也要用实验验证。一个可靠的流程是:

  1. 根据前三步选出2-3个候选度量。
  2. 在一个小的验证集或通过交叉验证,用你最终的评价指标(如聚类轮廓系数、分类准确率、检索的mAP值)来评估不同度量的效果。
  3. 结合计算效率,做出最终选择。

我自己的习惯是,对于新的数值型数据项目,标准化后的欧式距离余弦相似度总是我的第一批“试飞员”。一个衡量绝对距离,一个衡量相对模式,它们能快速给我一个基线认知。记住,没有放之四海而皆准的“最佳”距离,只有在特定数据和任务下的“最合适”距离。多试、多测,你的直觉会随着经验越来越准。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值