Anomaly Detection异常检测--PCA算法的实现

Anomaly Detection异常检测--PCA算法的实现

Principle Component Analysis是主成分分析,简称PCA。它的应用场景是对数据集进行降维。降维后的数据能够最大程度地保留原始数据的特征(以数据协方差为衡量标准)。
PCA的原理是通过构造一个新的特征空间,把原数据映射到这个新的低维空间里。PCA可以提高数据的计算性能,并且缓解"高维灾难"。高维灾难详见leiphone.com/news/20170
福利: 这里需要注意的一点是:经常有人把特征选择和特征抽取弄混。特征选择是直接对原始数据特征选择子集,具体的实现方法会在另一篇里写。而特征抽取会对原始数据特征进行变型,我们一般借助PCA方法来做特征抽取。
关于PCA的数学公式隆重推荐这篇blog.csdn.net/aspirinva

用PCA进行异常检测的原理是:PCA在做特征值分解之后得到的特征向量反应了原始数据方差变化程度的不同方向,特征值为数据在对应方向上的方差大小。所以,最大特征值对应的特征向量为数据方差最大的方向,最小特征值对应的特征向量为数据方差最小的方向。原始数据在不同方向上的方差变化反应了其内在特点。如果单个数据样本跟整体数据样本表现出的特点不太一致,比如在某些方向上跟其它数据样本偏离较大,可能就表示该数据样本是一个异常点。


具体实现步骤:

  1. 对于非异常label的数据取样,进行标准化处理,即scalar
standard_scalar = StandardScaler()
centered_training_data = standard_scalar.fit_transform(training_data[all_features])
  1. 初始化一个pca对象,用默认参数对所有成分进行保留。对标准化后的数据进行训练,得到基本的PCA模型。注意这里是无监督训练。
pca = PCA()
pca.fit(centered_training_data)
  1. 用pca()对步骤1中取样的数据(训练集)进行降维,计算数据样本在该方向上的偏离程度。
transformed_data = pca.transform(training_data)
y = transformed_data
lambdas = pca.singular_values_
M = ((y*y)/lambdas)

这里,y是降维后的数据集。虽说是降维,但因为我们在初始化PCA的时候是对所有参数进行了保留,所以这里的y可以理解为将原始的数据X映射到了一个新的空间:y=X转换矩阵。转换矩阵就是把特征向量按大小顺序从左往右排好组成的过渡矩阵。
这里,lambdas是训练集(training_data)的特征值集合。
M是数据样本的偏离程度矩阵。如果原始数据是500034,M还是5000*34。只不过M里的每一行数值代表了每个样本在重构空间里离每个特征向量的距离。
注意:这里的lambdas主要起归一化的作用,这样可以使得不同方向上的偏离程度具有可比性。在计算了数据样本在所有方向上的偏离程度之后,为了给出一个综合的异常得分,最自然的做法是将样本在所有方向上的偏离程度加起来。

  1. 计算主成分和次成分的阈值。
    'q' 设为可以让前q个成分解释数据集50%的方差
q = 5
print "Explained variance by first q terms: ", sum(pca.explained_variance_ratio_[:q])

r设为可以让r以后的成分对应的特征值小于0.2.

q_values = list(pca.singular_values_ < .2)
r = q_values.index(True)

根据r和q,对M进行切片,再对每个样本点进行距离的计算。np.sum(major_components, axis=1)就是在算每一行(样本)的距离加总。

major_components = M[:,range(q)]
minor_components = M[:,range(r, len(features))]
major_components = np.sum(major_components, axis=1)
minor_components = np.sum(minor_components, axis=1)

对切片后的数据集进行阈值计算

components = pd.DataFrame({'major_components': major_components, 
                               'minor_components': minor_components})
c1 = components.quantile(0.99)['major_components']
c2 = components.quantile(0.99)['minor_components']

这里的c1, c2是人为设定的两个阈值,如果得分大于阈值则判断为异常。
那么问题来了,我们不仅选了前50%最重要的成分,还选取了后面特征值小于0.2的最不重要的成分。为什么要这样做呢?原因如下:
一般而言,前几个特征向量往往直接对应原始数据里的某几个特征,在前几个特征向量方向上偏差比较大的数据样本,往往就是在原始数据中那几个特征上的极值点。而后几个特征向量有些不同,它们通常表示某几个原始特征的线性组合,线性组合之后的方差比较小反应了这几个特征之间的某种关系。在后几个特征方向上偏差比较大的数据样本,表示它在原始数据里对应的那几个特征上出现了与预计不太一致的情况。到底是考虑全部特征方向上的偏差,前几个特征向量上的偏差,还是后几个特征向量上的偏差,在具体使用时可以根据具体数据灵活处理。
当然,根据数据的情况,也可以只考虑数据在前 k 个特征向量方向上的偏差,或者只考虑后 r 个特征向量方向上的偏差。

  1. 计算出训练数据的阈值c1, c2,即上面得到的。
  2. 对于新的数据(测试集),用已经训练好的pca模型和standard_scalar模型进行transform。注意这里是用训练集的标准去tranform新的数据。
data = standard_scalar.transform(df_full[all_features])
transformed_data_test = pca.transform(data)
y_test = transformed_data
lambdas_test = pca.singular_values_
M_test = ((y*y)/lambdas)

然后r和q保持不变,还是用在训练集上算出来的r, q来对M_test进行切片

major_components_test = M_test[:,range(q)]
minor_components_test = M_test[:,range(r, len(features))]
major_components_test = np.sum(major_components, axis=1)
minor_components_test = np.sum(minor_components, axis=1)
  1. 制作分类器
def classifier(major_components, minor_components):  
    major = major_components > c1
    minor = minor_components > c2    
    return np.logical_or(major,minor)
  1. 计算结果
results = classifier(major_components=major_components, minor_components=minor_components)


更新:

最近发现了一篇写得很好的科普文,贴之, 与君共勉

blog.codinglabs.org/art

编辑于 2019-04-09 06:33