Ответ @Nipun Wijerathne показался мне неполным и немного запутанным, поэтому я решил предоставить MCVE для будущих читателей (MCVE в конце на самом деле :D), но сначала позвольте мне изложить некоторые общие рекомендации:
Решением является Расстояние Махаланобиса, которое делает что-то похожее на масштабирование признаков, беря собственные векторы переменных вместо исходной оси. Применяется следующая формула: в которой:
x
- это наблюдение для нахождения расстояния до негоm
- среднее наблюденийS
- ковариационная матрица.Рассмотрим набор данных 6x3 пример, в котором каждая строка представляет входные данные / пример, а каждый столбец представляет характеристику для примера:
Сначала нам нужно создать ковариационную матрицу признаков каждого входа, для чего в функции numpy.cov мы задаем параметр rowvar
равным False, поэтому каждый столбец представляет переменную:
covariance_matrix = np.cov(data, rowvar=False)
# data here looks similar to the above 2D / Matrix example in the pictures
Затем находим инверс ковариационной матрицы:
inv_covariance_matrix = np.linalg.inv(covariance_matrix)
Но прежде чем продолжить, мы должны проверить как упоминалось выше, являются ли матрица и ее обратная часть симметричными и положительно определенными, для этого мы используем алгоритм Cholesky Decomposition, к счастью, он уже реализован в numpy.linalg.cholesky:
def is_pos_def(A):
if np.allclose(A, A.T):
try:
np.linalg.cholesky(A)
return True
except np.linalg.LinAlgError:
return False
else:
return False
Далее мы находим среднее m
переменных по каждому признаку (можно сказать, измерению) и сохраняем их в массиве следующим образом:
vars_mean = []
for i in range(data.shape[0]):
vars_mean.append(list(data.mean(axis=0))) # axis=0 means each column in the 2D array
*Обратите внимание, что я повторил каждую строку только для того, чтобы воспользоваться вычитанием матрицы, как будет показано далее.
Далее мы находим x - m
(т.е. дифференциал), но мы уже векторизировали vars_mean
, поэтому все, что нам нужно сделать, это:
diff = data - vars_mean
# here we subtract the mean of feature from each feature of each example
]. Наконец, применим формулу следующим образом:
md = []
for i in range(len(diff)):
md.append(np.sqrt(diff[i].dot(inv_covariance_matrix).dot(diff[i])))
Обратите внимание на следующее:
number_of_features x number_of_features
.diff
аналогична матрице исходных данных: number_of_examples x number_of_features
.diff[i]
(т.е. строка) равен 1 x number_of_features
.diff[i].dot(inv_covariance_matrix)
будет 1 x number_of_features
, и когда мы снова умножаем на diff[i]
, numpy
автоматически считает последующую матрицу как матрицу столбцов, т.е. number_of_features x 1
, поэтому конечный результат станет единичным значением! (т.е. нет необходимости в транспонировании)k
, где k = 2.0 * std
для экстремальных значений и 3.0 * std
для очень экстремальных значений и это согласно правилу 68-95-99.7 (изображение для иллюстрации по той же ссылке):
import numpy as np
def create_data(examples=50, features=5, upper_bound=10, outliers_fraction=0.1, extreme=False):
'''
This method for testing (i.e. to generate a 2D array of data)
'''
data = []
magnitude = 4 if extreme else 3
for i in range(examples):
if (examples - i) <= round((float(examples) * outliers_fraction)):
data.append(np.random.poisson(upper_bound ** magnitude, features).tolist())
else:
data.append(np.random.poisson(upper_bound, features).tolist())
return np.array(data)
def MahalanobisDist(data, verbose=False):
covariance_matrix = np.cov(data, rowvar=False)
if is_pos_def(covariance_matrix):
inv_covariance_matrix = np.linalg.inv(covariance_matrix)
if is_pos_def(inv_covariance_matrix):
vars_mean = []
for i in range(data.shape[0]):
vars_mean.append(list(data.mean(axis=0)))
diff = data - vars_mean
md = []
for i in range(len(diff)):
md.append(np.sqrt(diff[i].dot(inv_covariance_matrix).dot(diff[i])))
if verbose:
print("Covariance Matrix:\n {}\n".format(covariance_matrix))
print("Inverse of Covariance Matrix:\n {}\n".format(inv_covariance_matrix))
print("Variables Mean Vector:\n {}\n".format(vars_mean))
print("Variables - Variables Mean Vector:\n {}\n".format(diff))
print("Mahalanobis Distance:\n {}\n".format(md))
return md
else:
print("Error: Inverse of Covariance Matrix is not positive definite!")
else:
print("Error: Covariance Matrix is not positive definite!")
def MD_detectOutliers(data, extreme=False, verbose=False):
MD = MahalanobisDist(data, verbose)
# one popular way to specify the threshold
#m = np.mean(MD)
#t = 3. * m if extreme else 2. * m
#outliers = []
#for i in range(len(MD)):
# if MD[i] > t:
# outliers.append(i) # index of the outlier
#return np.array(outliers)
# or according to the 68–95–99.7 rule
std = np.std(MD)
k = 3. * std if extreme else 2. * std
m = np.mean(MD)
up_t = m + k
low_t = m - k
outliers = []
for i in range(len(MD)):
if (MD[i] >= up_t) or (MD[i] <= low_t):
outliers.append(i) # index of the outlier
return np.array(outliers)
def is_pos_def(A):
if np.allclose(A, A.T):
try:
np.linalg.cholesky(A)
return True
except np.linalg.LinAlgError:
return False
else:
return False
data = create_data(15, 3, 10, 0.1)
print("data:\n {}\n".format(data))
outliers_indices = MD_detectOutliers(data, verbose=True)
print("Outliers Indices: {}\n".format(outliers_indices))
print("Outliers:")
for ii in outliers_indices:
print(data[ii])
data:
[[ 12 7 9]
[ 9 16 7]
[ 14 11 10]
[ 14 5 5]
[ 12 8 7]
[ 8 8 10]
[ 9 14 8]
[ 12 12 10]
[ 18 10 6]
[ 6 12 11]
[ 4 12 15]
[ 5 13 10]
[ 8 9 8]
[106 116 97]
[ 90 116 114]]
Covariance Matrix:
[[ 980.17142857 1143.62857143 1035.6 ]
[1143.62857143 1385.11428571 1263.12857143]
[1035.6 1263.12857143 1170.74285714]]
Inverse of Covariance Matrix:
[[ 0.03021777 -0.03563241 0.0117146 ]
[-0.03563241 0.08684092 -0.06217448]
[ 0.0117146 -0.06217448 0.05757261]]
Variables Mean Vector:
[[21.8, 24.6, 21.8], [21.8, 24.6, 21.8], [21.8, 24.6, 21.8], [21.8, 24.6, 21.8], [21.8, 24.6, 21.8], [21.8, 24.6, 21.8], [21.8, 24.6, 21.8], [21.8, 24.6, 21.8], [21.8, 24.6, 21.8], [21.8, 24.6, 21.8], [21.8, 24.6, 21.8], [21.8, 24.6, 21.8], [21.8, 24.6, 21.8], [21.8, 24.6, 21.8], [21.8, 24.6, 21.8]]
Variables - Variables Mean Vector:
[[ -9.8 -17.6 -12.8]
[-12.8 -8.6 -14.8]
[ -7.8 -13.6 -11.8]
[ -7.8 -19.6 -16.8]
[ -9.8 -16.6 -14.8]
[-13.8 -16.6 -11.8]
[-12.8 -10.6 -13.8]
[ -9.8 -12.6 -11.8]
[ -3.8 -14.6 -15.8]
[-15.8 -12.6 -10.8]
[-17.8 -12.6 -6.8]
[-16.8 -11.6 -11.8]
[-13.8 -15.6 -13.8]
[ 84.2 91.4 75.2]
[ 68.2 91.4 92.2]]
Mahalanobis Distance:
[1.3669401667524865, 2.1796331318432967, 0.7470525416547134, 1.6364973119931507, 0.8351423113609481, 0.9128858131134882, 1.397144258271586, 0.35603382066414996, 1.4449501739129382, 0.9668775289588046, 1.490503433100514, 1.4021488309805878, 0.4500345257064412, 3.239353067840299, 3.260149280200771]
Outliers Indices: [13 14]
Outliers:
[106 116 97]
[ 90 116 114]
В многомерных данных евклидово расстояние не работает, если существует ковариация между переменными (т.е. в вашем случае X, Y, Z).
Таким образом, расстояние Махаланобиса делает следующее,
Оно преобразует переменные в некоррелированное пространство.
Делает вариацию каждой переменной равной 1.
Затем вычислите простое евклидово расстояние.
Мы можем рассчитать расстояние Махаланобиса для каждой выборки данных следующим образом,
Здесь я привел код на языке python и добавил комментарии, чтобы вы могли понять код.
import numpy as np
data= np.matrix([[1, 2, 3, 4, 5, 6, 7, 8],[1, 4, 9, 16, 25, 36, 49, 64],[1, 4, 9, 16, 25, 16, 49, 64]])
def MahalanobisDist(data):
covariance_xyz = np.cov(data) # calculate the covarince matrix
inv_covariance_xyz = np.linalg.inv(covariance_xyz) #take the inverse of the covarince matrix
xyz_mean = np.mean(data[0]),np.mean(data[1]),np.mean(data[2])
x_diff = np.array([x_i - xyz_mean[0] for x_i in x]) # take the diffrence between the mean of X variable the sample
y_diff = np.array([y_i - xyz_mean[1] for y_i in y]) # take the diffrence between the mean of Y variable the sample
z_diff = np.array([z_i - xyz_mean[2] for z_i in z]) # take the diffrence between the mean of Z variable the sample
diff_xyz = np.transpose([x_diff, y_diff, z_diff])
md = []
for i in range(len(diff_xyz)):
md.append(np.sqrt(np.dot(np.dot(np.transpose(diff_xyz[i]),inv_covariance_xyz),diff_xyz[i]))) #calculate the Mahalanobis Distance for each data sample
return md
def MD_removeOutliers(data):
MD = MahalanobisDist(data)
threshold = np.mean(MD) * 1.5 # adjust 1.5 accordingly
outliers = []
for i in range(len(MD)):
if MD[i] > threshold:
outliers.append(i) # index of the outlier
return np.array(outliers)
print(MD_removeOutliers(data))
Надеюсь, это поможет.
Ссылки,