卡方距离(Chi-square Measure)这个听起来有点学术范儿的名字,其实来源于统计学中的卡方检验(χ²检验)。我第一次接触这个概念是在处理文本分类项目时,当时需要从成千上万个词语中筛选出最有区分度的特征。导师随口说了句"试试卡方检验",结果让我打开了新世界的大门。
1900年,英国统计学家Karl Pearson提出了这个统计方法,用来检验观察数据与理论分布之间的差异。简单来说,它衡量的是"实际看到的数据"和"理论上应该看到的数据"之间的差距。举个例子,假设我们抛硬币100次,理论上应该得到50次正面和50次反面。如果实际结果是60:40,卡方检验就能告诉我们这个差异是否显著。
在机器学习中,卡方距离的计算公式是这样的:
python复制def chi_square_distance(x, y):
import numpy as np
x = np.asarray(x, np.int32)
y = np.asarray(y, np.int32)
return np.sum(np.square(x - y) / y)
这个公式的核心思想是:对每个类别的观测值和期望值之差取平方,然后除以期望值再求和。为什么要除以期望值呢?这是为了标准化,使得不同规模的类别可以公平比较。我在实际项目中经常发现,很多初学者会忽略这个标准化步骤,导致结果出现偏差。
记得我第一次用卡方距离做特征选择时,数据集有超过2万个词语特征。使用传统的欧氏距离效果很差,因为词频数据通常非常稀疏且分布不均匀。卡方距离的优势在于它专门处理这种计数型数据,能够有效衡量特征与类别之间的相关性。
具体来说,卡方距离在特征选择中的应用原理是:计算每个特征与目标类别之间的独立性。如果某个特征在不同类别中的分布差异很大,说明这个特征对分类很有帮助。比如在垃圾邮件分类中,"免费"这个词在垃圾邮件中出现的频率可能远高于正常邮件,卡方值就会很高。
下面是我在一个电商评论情感分析项目中使用的代码片段:
python复制from sklearn.feature_selection import SelectKBest, chi2
# X是文本的TF-IDF向量,y是情感标签(0负面/1正面)
selector = SelectKBest(chi2, k=500)
X_new = selector.fit_transform(X, y)
# 查看得分最高的特征
feature_scores = pd.DataFrame({
'feature': vectorizer.get_feature_names_out(),
'score': selector.scores_
}).sort_values('score', ascending=False)
这个例子中,我们从原始的高维特征空间中选出了500个最具区分度的词语。实际运行后发现,像"质量差"、"不满意"这样的负面词语,以及"推荐"、"物超所值"这样的正面词语都获得了很高的卡方分数。
在基于内容的图像检索(CBIR)系统中,颜色直方图是最常用的特征之一。但直接用欧氏距离比较直方图有个问题:它会把所有bin的差异同等对待。而实际上,高频颜色bin的小差异可能比低频颜色bin的大差异更重要。
卡方距离在这里的优势就体现出来了。因为它考虑了每个bin的期望频率,能够更合理地衡量直方图差异。我做过一个实验,比较欧氏距离和卡方距离在图像检索中的表现:
| 距离度量 | 准确率 | 召回率 | 平均排名 |
|---|---|---|---|
| 欧氏距离 | 0.72 | 0.68 | 4.2 |
| 卡方距离 | 0.85 | 0.82 | 2.5 |
在OpenCV中,我们可以这样实现基于卡方距离的图像检索:
python复制import cv2
def compare_histograms(hist1, hist2):
# 归一化直方图
hist1 = cv2.normalize(hist1, None).flatten()
hist2 = cv2.normalize(hist2, None).flatten()
# 计算卡方距离
return cv2.compareHist(hist1, hist2, cv2.HISTCMP_CHISQR)
# 实际应用示例
query_hist = calc_histogram(query_image)
database_hists = [calc_histogram(img) for img in database_images]
# 找出最相似的图像
distances = [compare_histograms(query_hist, db_hist) for db_hist in database_hists]
most_similar_idx = np.argmin(distances)
这里有个小技巧:在计算直方图前,最好先对图像进行颜色量化,减少颜色空间维度。我通常会把RGB空间的256^3种颜色量化到64或128个主色调,这样既能保持区分度,又能提高计算效率。
卡方距离公式中的分母是期望频数,如果某个类别的期望频数为零,就会导致除零错误。我在处理社交媒体数据时就遇到过这个问题:某些小众标签在测试集中完全没有出现。
解决方案是使用平滑技术,常见的有:
python复制# 带平滑的卡方距离实现
def smoothed_chi_square(x, y, alpha=1.0):
y_smooth = y + alpha
return np.sum(np.square(x - y) / y_smooth)
卡方距离不是万能的,我整理了一个简单对比表格:
| 场景 | 推荐距离度量 | 原因 |
|---|---|---|
| 文本特征选择 | 卡方距离 | 擅长处理稀疏的计数数据 |
| 图像直方图比较 | 卡方距离或巴氏距离 | 考虑分布形状,对光照变化鲁棒 |
| 高维稠密向量 | 余弦相似度 | 不受向量长度影响,适合文本/图像嵌入 |
| 空间坐标数据 | 欧氏距离 | 符合几何直觉 |
| 类别型数据 | 汉明距离 | 直接比较差异位数 |
在实际项目中,我通常会尝试多种距离度量,通过交叉验证来选择最佳方案。有时候将不同距离组合使用也能取得意想不到的效果。比如在商品推荐系统中,我同时使用卡方距离(用于用户行为统计)和余弦相似度(用于商品描述嵌入),加权后的效果比单一距离要好很多。