• https://www.jianshu.com/p/e35a47913457https://zhuanlan.zhihu.com/p/29091645
    为了统一整个语雀空间其他部分异常检测,这里给出PCA使用场景:多对象多特征,对象可重复。例如异常登录检测,一段时间内有多个账号登录,账号可以重复登录。第一步:将账号进行分组统计,在输入PCA前保证矩阵一行一个样本(相当于取了对象唯一值)。">参考 https://www.jianshu.com/p/e35a47913457https://zhuanlan.zhihu.com/p/29091645
    为了统一整个语雀空间其他部分异常检测,这里给出PCA使用场景:多对象多特征,对象可重复。例如异常登录检测,一段时间内有多个账号登录,账号可以重复登录。第一步:将账号进行分组统计,在输入PCA前保证矩阵一行一个样本(相当于取了对象唯一值)。

    参考 https://www.jianshu.com/p/e35a47913457https://zhuanlan.zhihu.com/p/29091645
    为了统一整个语雀空间其他部分异常检测,这里给出PCA使用场景:多对象多特征,对象可重复。例如异常登录检测,一段时间内有多个账号登录,账号可以重复登录。第一步:将账号进行分组统计,在输入PCA前保证矩阵一行一个样本(相当于取了对象唯一值)。

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

    1. from sklearn.preprocessing import StandardScaler
    2. from sklearn.decomposition import PCA
    3. standard_scalar = StandardScaler()
    4. centered_training_data = standard_scalar.fit_transform(training_data[all_features]) # 先标准化特征
    5. pca = PCA()
    6. pca.fit(centered_training_data)
    7. transformed_data = pca.transform(training_data)
    8. y = transformed_data
    9. lambdas = pca.singular_values_
    10. M = ((y*y)/lambdas) # 计算数据样本在该方向上的偏离程度

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

    1. #计算主成分和次成分的阈值。 'q' 设为可以让前q个成分解释数据集50%的方差
    2. q = 5
    3. print("Explained variance by first q terms: ", sum(pca.explained_variance_ratio_[:q]) )
    4. # r设为可以让r以后的成分对应的特征值小于0.2.
    5. q_values = list(pca.singular_values_ < .2)
    6. r = q_values.index(True)
    7. # 根据r和q,对M进行切片,再对每个样本点进行距离的计算。np.sum(major_components, axis=1)就是在算每一行(样本)的距离加总。
    8. major_components = M[:,range(q)]
    9. minor_components = M[:,range(r, len(features))]
    10. major_components = np.sum(major_components, axis=1)
    11. minor_components = np.sum(minor_components, axis=1)
    12. # 对切片后的数据集进行阈值计算
    13. components = pd.DataFrame({'major_components': major_components,
    14. 'minor_components': minor_components})
    15. c1 = components.quantile(0.99)['major_components']
    16. c2 = components.quantile(0.99)['minor_components']

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

    对于新的数据(测试集),用已经训练好的pca模型和standard_scalar模型进行transform。注意这里是用训练集的标准去tranform新的数据。

    1. data = standard_scalar.transform(df_full[all_features])
    2. transformed_data_test = pca.transform(data)
    3. y_test = transformed_data
    4. lambdas_test = pca.singular_values_
    5. M_test = ((y*y)/lambdas)

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

    1. major_components_test = M_test[:,range(q)]
    2. minor_components_test = M_test[:,range(r, len(features))]
    3. major_components_test = np.sum(major_components, axis=1)
    4. minor_components_test = np.sum(minor_components, axis=1)

    制作分类器

    1. def classifier(major_components, minor_components):
    2. major = major_components > c1
    3. minor = minor_components > c2
    4. return np.logical_or(major,minor)

    计算结果

    1. results = classifier(major_components=major_components, minor_components=minor_components)