浅谈马氏距离【Mahalonobis Distance】
1. Introduction
马氏距离是度量某个点和某个分布间距离的一个指标。在异常值检测、高度不平衡数据的分类以及 one-class 分类上都有着广泛的应用。本文将解释马氏距离背后的intuition以及用一些实例帮助大家更好地理解这个指标。
2. 欧式距离对于多元数据会存在一些什么问题?
在介绍马氏距离之前,我想有必要对欧式距离进行一些简单的阐述。欧式距离是我们衡量两个点之间距离的最常用的指标。
如果两个点在一个二维平面中,那么对于这两个点 (p1,q1) 和 (p2,q2) 之间的欧式距离可以表示为:
对于多维空间,上述式子可以拓展为:
当然,只要维数(列)equally weighted并且相互独立,欧式距离仍是不错的选择。
这句话是什么意思呢,接下来举个例子。
这两个表显示了相同对象的“Area”和“Price”,仅仅是变量的单位发生了改变。理论上,任意两行(可以理解为A点和B点)之间的距离都是相同的,但是欧式距离计算的话就会产生不同的值。当然,这个问题可以从技术上加以克服,比如对它进行标准化处理((x – mean) / std),使其范围在0到1之间。
但是,欧式距离还有一个缺点。如果维度(数据集中的列)相互关联(这在实际数据集中通常是这种情况),那么点和分布间的欧式距离对于这个点到底离分布多近就会给出一些误导信息。
右上图给出了两个正相关变量的散点图。当一个变量增加时,另一个变量也会增加。图中蓝点和红点与中心的(欧式)距离相等,但实际上只有蓝色才是更接近cluster的。
这是因为,欧式距离刻画的仅仅是两个点之间的距离,它并不考虑数据集中的其余点是如何变化的。因此,欧式距离是不能用来真正的判断一个点与点的分布到底有多接近的。
这就引入了马氏距离。
3 .什么是马氏距离
马氏距离是点和分布间点距离,并不是两个点之间的距离,它其实是欧式距离的多元等效项。
那么在计算上,马氏距离到底与欧式距离有何不同呢?
(1) 它将“列”转化成不相关的向量。
(2) 缩放列使其方差等于1。
(3) 最后计算欧式距离
这三个步骤很好地解决了刚才所谈的关于欧式距离的一些缺点。接下来我们来详细了解一下马氏距离。
4.马氏距离背后的数学和intuition
首先,奉上马氏距离的公式:
这里,D^2 即为马氏距离;x表示观察到的向量(数据集的row);m表示独立变量(列)的均值向量;C^(-1)表示的是独立变量协方差矩阵的逆。
scale
(x-m)实际上是向量与均值的距离,然后除以协方差矩阵。这个形式有没有很眼熟?这实际上是标准化的多元等价形式。z = (x vector) - (mean vector) / (covariance matrix)
correlation
那除以协方差有什么作用呢?
如果数据集中的变量是高度相关的,那么会导致协方差很大,除以较大的协方差就可以有效地减小距离。那如果X并不相关,那么协方差也不大,因此距离也不会减小很多。
综上所述,马氏距离可以有效地解决欧式距离的量纲和相关性的问题。
5. 利用python来计算马氏距离
import pandas as pd
import scipy as sp
import numpy as np
filepath = 'https://raw.githubusercontent.com/selva86/datasets/master/diamonds.csv'
df = pd.read_csv(filepath).iloc[:, [0,4,6]]
df.head()
接下来写计算马氏距离的公式。
def mahalanobis(x=None, data=None, cov=None):
"""Compute the Mahalanobis Distance between each row of x and the data
x : vector or matrix of data with, say, p columns.
data : ndarray of the distribution from which Mahalanobis distance of each observation of x is to be computed.
cov : covariance matrix (p x p) of the distribution. If None, will be computed from data.
"""
x_minus_mu = x - np.mean(data)
if not cov:
cov = np.cov(data.values.T)
inv_covmat = sp.linalg.inv(cov)
left_term = np.dot(x_minus_mu, inv_covmat)
mahal = np.dot(left_term, x_minus_mu.T)
return mahal.diagonal()
df_x = df[['carat', 'depth', 'price']].head(500)
df_x['mahala'] = mahalanobis(x=df_x, data=df[['carat', 'depth', 'price']])
df_x.head()
6. Case1: 使用马氏距离进行多元异常值检测
假设检验统计量遵循自由度为n的卡方分布,则在0.01显著性水平和2自由度下的临界值计算如下:
# Critical values for two degrees of freedom
from scipy.stats import chi2
chi2.ppf