数学家的预测。 我们分析了检测异常的主要方法

在工业中将人工智能用于各种系统的预测性维护在国外越来越受欢迎。 该方法的目的是在操作阶段确定系统运行中的故障,直到系统出现故障为止,以便及时做出响应。

这种方法在我国和西方有多重要? 例如,可以在有关《哈布雷》和《媒介》的文章中得出结论。 在Habré上几乎没有关于解决预测性维护问题的文章。 在Medium上有一整套。 在这里, 这里这里都很好地描述了这种方法的目标和优势。

通过本文,您将学到:

  • 为什么需要这种技术
  • 哪种机器学习方法更常用于预测性维护,
  • 我如何通过一个简单的例子尝试其中的一些技巧。

来源

预测服务提供哪些功能?

  • 控制修理工作的过程,必要时进行,从而节省了金钱,而且不着急,从而改善了这些工作的质量
  • 识别设备运行中的特定故障(在设备运行时购买特定零件进行更换的能力提供了巨大的优势);
  • 优化设备操作,负荷等;
  • 减少定期关闭设备的成本。

下一篇有关Medium的文章很好地描述了需要理解的问题,以了解如何在特定情况下解决此问题。

在收集数据或选择数据以构建模型时,重要的是要回答三组问题:

  1. 是否可以预测所有系统问题? 哪个预测特别重要?
  2. 什么是失败过程? 整个系统会停止工作还是仅更改工作模式? 它是一个快速的过程,是瞬时的还是逐渐的退化?
  3. 系统性能是否足以反映其性能? 它们与系统的各个部分或整个系统有关吗?

事先了解您要预测的内容,可以预测的内容和不能预测的内容也很重要。

关于Medium的文章还列出了有助于确定您的特定目标的问题:

  • 需要预测什么? 剩余寿命,是否有异常行为,在接下来的N小时/天/周内发生故障的概率是多少?
  • 是否有足够的历史数据?
  • 是否知道系统何时给出异常读数,何时不给出。 是否可以标记此类指示?
  • 模型应该看多远? 读数在一小时/天/周的间隔中反映系统运行的独立性如何
  • 您需要优化什么? 该模型应该在发出错误警报的同时捕获尽可能多的违规,还是足以捕获无误报的多个事件。

希望将来情况会有所改善。 迄今为止,在预测性维护领域中存在困难:很少有系统故障或系统故障时刻的例子,但它们没有被标记; 故障过程是未知的。

解决预测维护困难的主要途径是使用异常搜索方法 。 这样的算法不需要标记来进行训练。 对于测试和调试算法,必须以一种或另一种形式进行标记。 这种方法的局限性在于它们不能预测特定的故障,而只能指示指示器的异常。

但这已经不错。

来源

方法


现在,我想谈谈异常检测方法的一些功能,然后我们将一起在实践中测试一些简单算法的功能。

尽管在特定情况下将需要测试几种算法以查找异常并选择最佳算法,但是有可能确定该领域中使用的主要技术的一些优点和缺点。

首先,重要的是要事先了解数据中异常的百分比。

如果我们谈论的是半监督方法的一种变体(我们仅研究“正常”数据,然后进行工作(测试),然后研究异常数据),那么最佳选择就是一类支持向量方法( One-Class SVM 。 当使用径向基函数作为内核时,此算法在原点周围构造一个非线性曲面。 训练数据越干净,效果越好。

在其他情况下,仍然需要知道异常点与“正常”点的比率-确定截止阈值。

如果数据中异常的数量超过5%,并且可以与主样本很好地分离,则可以使用标准的异常搜索方法。

在这种情况下,就质量而言, 隔离林方法是最稳定的: 隔离林是随机数据。 更具特征性的指示更有可能更深入,而不寻常的指示则将在第一次迭代中与其余样本分离。

如果其他算法“适合”数据的具体情况,则效果更好。

当数据具有正态分布时, 椭圆包络法是合适的,以多维正态分布近似数据。 该点属于分布的可能性越小,则该点异常的可能性就越大。

如果以这样的方式显示数据:不同点的相对位置很好地反映了它们的差异,那么度量方法似乎是一个不错的选择:例如, k个最近的邻居,第k个最近的邻居,ABOD(基于角度的离群值检测)或LOF(局部离群值因子) )

所有这些方法表明,“正确”的指标集中在多维空间的一个区域。 如果在第k个(或第k个)最近的邻居中,所有事物都离目标较远,则该点就是一个异常。 对于ABOD,其推理类似:如果相对于所考虑的k个点,所有k个最近的点都在相同的空间扇区中,则该点是异常的。 对于LOF:如果局部密度(对于每个点,由k个最近的邻居确定)低于k个最近的邻居,则该点是异常的。

如果数据聚类良好,则基于聚类分析的方法是一个不错的选择。 如果该点与几个群集的中心等距,则为异常。

如果在数据中很好地区分了最大方差变化的方向,那么基于主成分法搜索异常似乎是一个不错的选择。 在这种情况下,将n1(最“主要”分量)和n2(最不“主要”分量)的平均值偏差视为异常度量。

例如,建议查看The Prognostics and Health Management Society(PHM Society)的数据集。 这个非营利组织每年都会安排比赛。 例如,在2018年, 需要预测操作错误以及离子束蚀刻厂发生故障之前的时间 。 我们将采用2015年数据集 。 它包含用于30个安装的多个传感器的读数(培训样本),并且需要预测何时以及将发生什么错误。

我没有在网络上找到测试样本的答案,因此我们只会训练一个。

通常,所有设置都是相似的,但在组件数量,异常数量等方面有所不同。 因此,在前20个学习,而在其他20个测试则没有太大意义。

因此,我们将选择一种安装方式,将其加载并查看这些数据。 本文将不涉及功能工程 ,因此我们不会过多关注。

import pandas as pd import matplotlib.pyplot as plt %matplotlib inline import seaborn as sns from sklearn.covariance import EllipticEnvelope from sklearn.neighbors import LocalOutlierFactor from sklearn.ensemble import IsolationForest from sklearn.svm import OneClassSVM dfa=pd.read_csv('plant_12a.csv',names=['Component number','Time','S1','S2','S3','S4','S1ref','S2ref','S3ref','S4ref']) dfa.head(10) 


如您所见,有七个组件,每个组件每15分钟读取四个传感器的读数。 比赛说明中的S1ref-S4ref作为参考值列出,但这些值与传感器的读数有很大不同。 为了不浪费时间思考它们的含义,我们将其删除。 如果查看每个要素(S1-S4)的值分布,结果发现S1,S2和S4的分布是连续的,而S3的分布是离散的。 此外,如果查看S2和S4的联合分布,结果发现它们成反比。


尽管偏离直接依赖关系可能表明存在错误,但我们将不检查此错误,而只是删除S4。

我们再次处理数据集。 离开S1,S2和S3。 我们使用StandardScaler缩放S1和S2(减去平均值并除以标准差),将S3转换为OHE(一次热编码)。 我们将所有安装组件的读数缝在一起。 共有89个功能。 2 * 7 = 14-7个组件的读数S1和S2和R3的75个唯一值。 只有5.6万条这样的线路。

上载有错误的文件。

 dfc=pd.read_csv('plant_12c.csv',names=['Start Time', 'End Time','Type']) dfc.head() 


在我们的数据集上尝试这些算法之前,我将允许自己再做一个小小的题外话。 您需要测试。 为此,建议采用错误的开始时间和结束时间。 并且在此间隔内的所有指示均被视为异常,而在外部-正常。 这种方法有许多缺点。 但是尤其是一种错误行为很可能发生在错误修复之前。 为了保真,让我们在半小时前及时转移异常窗口。 我们将评估F1量度,精度和召回率。

用于区分功能和确定模型质量的代码:

 def load_and_preprocess(plant_num):   #      ,       dfa=pd.read_csv('plant_{}a.csv'.format(plant_num),names=['Component number','Time','S1','S2','S3','S4','S1ref','S2ref','S3ref','S4ref'])   dfc=pd.read_csv('plant_{}c.csv'.format(plant_num),names=['Start Time','End Time','Type']).drop(0,axis=0)   N_comp=len(dfa['Component number'].unique())   #  15    dfa['Time']=pd.to_datetime(dfa['Time']).dt.round('15min')   #  6    (  ,    )   dfc=dfc[dfc['Type']!=6]   dfc['Start Time']=pd.to_datetime(dfc['Start Time'])   dfc['End Time']=pd.to_datetime(dfc['End Time'])   #      ,       OHE  3-    dfa=pd.concat([dfa.groupby('Time').nth(i)[['S1','S2','S3']].rename(columns={"S1":"S1_{}".format(i),"S2":"S2_{}".format(i),"S3":"S3_{}".format(i)}) for i in range(N_comp)],axis=1).dropna().reset_index()   for k in range(N_comp):       dfa=pd.concat([dfa.drop('S3_'+str(k),axis=1),pd.get_dummies(dfa['S3_'+str(k)],prefix='S3_'+str(k))],axis=1).reset_index(drop=True)   #          df_train,df_test=train_test_split(dfa,test_size=0.25,shuffle=False)   cols_to_scale=df_train.filter(regex='S[1,2]').columns   scaler=preprocessing.StandardScaler().fit(df_train[cols_to_scale])   df_train[cols_to_scale]=scaler.transform(df_train[cols_to_scale])   df_test[cols_to_scale]=scaler.transform(df_test[cols_to_scale])   return df_train,df_test,dfc #       def get_true_labels(measure_times,dfc,shift_delta):   idxSet=set()   dfc['Start Time']-=pd.Timedelta(minutes=shift_delta)   dfc['End Time']-=pd.Timedelta(minutes=shift_delta)   for idx,mes_time in tqdm_notebook(enumerate(measure_times),total=measure_times.shape[0]):       intersect=np.array(dfc['Start Time']<mes_time).astype(int)*np.array(dfc['End Time']>mes_time).astype(int)       idxs=np.where(intersect)[0]       if idxs.shape[0]:           idxSet.add(idx)   dfc['Start Time']+=pd.Timedelta(minutes=shift_delta)   dfc['End Time']+=pd.Timedelta(minutes=shift_delta)   true_labels=pd.Series(index=measure_times.index)   true_labels.iloc[list(idxSet)]=1   true_labels.fillna(0,inplace=True)   return true_labels #          def check_model(model,df_train,df_test,filt='S[123]'):   model.fit(df_train.drop('Time',axis=1).filter(regex=(filt)))   y_preds = pd.Series(model.predict(df_test.drop(['Time','Label'],axis=1).filter(regex=(filt)))).map({-1:1,1:0})   print('F1 score: {:.3f}'.format(f1_score(df_test['Label'],y_preds)))   print('Precision score: {:.3f}'.format(precision_score(df_test['Label'],y_preds)))   print('Recall score: {:.3f}'.format(recall_score(df_test['Label'],y_preds)))   score = model.decision_function(df_test.drop(['Time','Label'],axis=1).filter(regex=(filt)))   sns.distplot(score[df_test['Label']==0])   sns.distplot(score[df_test['Label']==1]) df_train,df_test,anomaly_times=load_and_preprocess(12) df_test['Label']=get_true_labels(df_test['Time'],dfc,30) 

PHM 2015 Data Challenge数据集上简单异常搜索算法的测试结果

回到算法。 让我们在数据上尝试一个Class SVM(OCSVM),IsolationForest(IF),EllipticEnvelope(EE)和LocalOutlierFactor(LOF)。 首先,我们将不设置任何参数。 我注意到LOF可以在两种模式下工作。 如果novellity = False只能在训练集中搜索异常(只有fit_predict),如果为True,则其目的是在训练集中搜索异常(可以单独拟合和预测)。 IF具有旧的和新的行为模式。 我们使用新的。 他给出了更好的结果。

OCSVM可以很好地检测到异常,但是假阳性过多。 对于其他方法,结果甚至更糟。

但是假设我们知道数据中异常的百分比。 在我们的案例中,为27%。 OCSVM具有nu-误差百分比的上限估计值,而支持向量的百分比下限值。 其他污染方法具有一定百分比的数据错误。 在IF和LOF方法中,它是自动确定的,而对于OCSVM和EE,默认情况下将其设置为0.1。 让我们尝试将污染(nu)设置为0.27。 现在是EE的最佳结果。

用于检查模型的代码:

 def check_model(model,df_train,df_test,filt='S[123]'):   model_type,model = model   model.fit(df_train.drop('Time',axis=1).filter(regex=(filt)))   y_preds = pd.Series(model.predict(df_test.drop(['Time','Label'],axis=1).filter(regex=(filt)))).map({-1:1,1:0})   print('F1 score for {}: {:.3f}'.format(model_type,f1_score(df_test['Label'],y_preds)))   print('Precision score for {}: {:.3f}'.format(model_type,precision_score(df_test['Label'],y_preds)))   print('Recall score for {}: {:.3f}'.format(model_type,recall_score(df_test['Label'],y_preds)))   score = model.decision_function(df_test.drop(['Time','Label'],axis=1).filter(regex=(filt)))   sns.distplot(score[df_test['Label']==0])   sns.distplot(score[df_test['Label']==1])   plt.title('Decision score distribution for {}'.format(model_type))   plt.show() 

查看不同方法的异常指标的分布很有趣。 可以看出,LOF不适用于此数据。 EE指出该算法认为异常异常。 但是,正常点在那里。 IsoFor和OCSVM表明,选择截止阈值(污染度/ nu)很重要,这将改变准确性和完整性之间的权衡。


传感器的读数接近正态分布,接近固定值,这是合乎逻辑的。 如果我们确实有一个标记过的测试样品,最好还有一个经过验证的样品,那么可以对污染值进行着色。 下一个问题是,哪些错误的指向更准确:误报还是误报?

LOF结果非常低。 不太令人印象深刻。 但是请记住,OHE变量与StandardScaler转换的变量一起进入输入。 并且默认距离是欧几里得。 但是,如果仅根据S1和S2计算变量,则可以纠正这种情况,并且结果可以与其他方法进行比较。 但是,重要的是要了解列出的度量标准分类器的关键参数之一是邻居数。 它会严重影响质量,必须对其进行调整。 距离度量本身也很不错。

现在尝试结合两个模型。 一开始,我们从训练集中删除异常。 然后,我们将在“更干净”的训练集上训练OCSVM。 根据以前的结果,我们观察到了EE的最大完整性。 我们通过EE清除训练样本,对其进行训练OCSVM,得到F1 = 0.50,准确度= 0.34,完整性= 0.95。 不令人印象深刻。 但是我们只是要求nu = 0.27。 我们拥有的数据或多或少是“干净的”。 假设训练样本与EE具有相同的完整性,那么将保留5%的错误。 我们将自己设置为nu,得到F1 = 0.69,准确度= 0.59,完整性= 0.82。 太好了 重要的是要注意,在其他方法中,这样的组合将不起作用,因为它们暗示训练集中的异常数和测试数相同。 当在纯训练数据集上训练这些方法时,您将必须指定比真实数据少的污染,并且不接近零,但是最好选择它进行交叉验证。

查看指示序列上的搜索结果很有趣:


该图显示了7个组件的第一传感器和第二传感器的读数的一部分。 在图例中,相应错误的颜色(开始和结束由相同颜色的垂直线显示)。 点表示预测:绿色-真实预测,红色-假阳性,紫色-假阴性。 从图中可以看出,很难从视觉上确定错误时间,并且该算法很好地解决了这一任务。 尽管很重要的一点是,此处没有给出第三个传感器的读数。 此外,错误结束后还会出现假阳性读数。 即 该算法发现还有错误值,因此我们将该区域标记为无错误。 图的右侧显示了错误之前的区域,我们将其标记为错误的区域(错误发生前半小时),该区域被认为是无错误的,从而导致了假阴性模型错误。 在图的中心,识别出一个连贯的部分,被认为是一个错误。 结论可以得出如下:在解决搜索异常的问题时,您需要与了解需要预测其输出的系统本质的工程师紧密互动,因为检查标记上使用的算法并不能完全反映现实情况,并且无法模拟此类算法可以满足的条件。被使用。

绘制图表的代码:

 def plot_time_course(df_test,dfc,y_preds,start,end,vert_shift=4):   plt.figure(figsize=(15,10))   cols=df_train.filter(regex=('S[12]')).columns   add=0   preds_idx=y_preds.iloc[start:end][y_preds[0]==1].index   true_idx=df_test.iloc[start:end,:][df_test['Label']==1].index   tp_idx=set(true_idx.values).intersection(set(preds_idx.values))   fn_idx=set(true_idx.values).difference(set(preds_idx.values))   fp_idx=set(preds_idx.values).difference(set(true_idx.values))   xtime=df_test['Time'].iloc[start:end]   for col in cols:       plt.plot(xtime,df_test[col].iloc[start:end]+add)       plt.scatter(xtime.loc[tp_idx].values,df_test.loc[tp_idx,col]+add,color='green')       plt.scatter(xtime.loc[fn_idx].values,df_test.loc[fn_idx,col]+add,color='violet')       plt.scatter(xtime.loc[fp_idx].values,df_test.loc[fp_idx,col]+add,color='red')       add+=vert_shift   failures=dfc[(dfc['Start Time']>xtime.iloc[0])&(dfc['Start Time']<xtime.iloc[-1])]   unique_fails=np.sort(failures['Type'].unique())   colors=np.array([np.random.rand(3) for fail in unique_fails])   for fail_idx in failures.index:       c=colors[np.where(unique_fails==failures.loc[fail_idx,'Type'])[0]][0]       plt.axvline(failures.loc[fail_idx,'Start Time'],color=c)       plt.axvline(failures.loc[fail_idx,'End Time'],color=c)   leg=plt.legend(unique_fails)   for i in range(len(unique_fails)):       leg.legendHandles[i].set_color(colors[i]) 

如果异常百分比低于5%和/或它们与“正常”指标的分离度很差,则上述方法效果不佳,值得使用基于神经网络的算法。 在最简单的情况下,这些将是:

  • 自动编码器(受过训练的自动编码器的高错误将表示读数异常);
  • 递归网络(按顺序学习以预测最后的读数。如果相差很大-表示异常)。

另外,值得注意的是使用时间序列的细节。 重要的是要了解,以上大多数算法(自动编码器和隔离林除外)在添加滞后特征(从先前时间点读取的数据)时可能会给出较差的质量。

让我们尝试在示例中添加滞后特征。 竞赛说明说,错误发生3小时之前的值与错误没有任何关系。 然后在3小时内添加标志。 共有259个标志。

结果,OCSVM和IsolationForest的结果几乎保持不变,而椭圆形信封和LOF的结果下降。

要使用有关系统动力学的信息,应使用具有递归或卷积神经网络的自动编码器。 或者,例如,自动编码器,压缩信息和基于压缩信息来搜索异常的常规方法的组合。 反向方法似乎也很有希望。 通过标准算法对最不典型的点进行初步筛选,然后在更干净的数据上训练自动编码器。

来源

有一套处理一维时间序列的技术。 所有这些都旨在预测未来的读数,与预测背离的点被视为异常。

Holt-Winters模型


三重指数平滑,将序列分为3个部分:水平,趋势和季节性。 因此,如果以这种形式呈现系列,则该方法效果很好。 Facebook Prophet的工作原理类似,但是以不同的方式评估组件本身。 例如,可以在此处阅读更多详细信息。

S(ARIMA)


在这种方法中,预测模型基于自回归和移动平均值。 如果我们谈论的是S(ARIMA)的扩展,那么它使我们可以评估季节性。 在此处此处此处阅读有关该方法的更多信息。

其他预测服务方法


当涉及到时间序列并且有关于错误发生时间的信息时,您可以与老师一起应用教学方法。 在这种情况下,除了需要标记数据外,重要的是要了解错误预测将取决于错误的性质。 如果存在许多错误且性质不同,则很可能有必要分别预测每个错误,这将需要更多标记数据,但前景会更具吸引力。

在预测性维护中还有其他使用机器学习的方法。 例如,预测未来N天的系统故障(分类任务)。 重要的是要理解,这种方法要求系统操作中的错误之前要经过一段时间的降级(不一定是渐进的)。 在这种情况下,最成功的方法似乎是使用具有卷积和/或递归层的神经网络。 另外,值得注意的是增加时间序列的方法。 在我看来, 两种方法最有趣,同时又很简单:

  • 选择该行的连续部分(例如70%,其余部分被删除)并拉伸到原始大小
  • 选择该行的连续部分(例如20%)并拉伸或压缩。 之后,整个行将相应地压缩或拉伸到其原始大小。

还有一个选项可以预测剩余的系统寿命(回归任务)。 在这里,我们可以区分一种单独的方法:预测的不是寿命,而是威布尔分布参数。

您可以 此处了解有关分布本身的信息 ,以及与循环网格结合使用的信息。 该分布具有两个参数α和β。 α表示事件何时发生,β表示算法的信心。 尽管这种方法的应用前景广阔,但在这种情况下训练神经网络会出现困难,因为与起预测适当寿命相比,算法一开始不安全很容易。

另外,值得注意的是Cox回归 。 它使您可以在诊断后预测每个时间点的系统容错能力,并将其表示为两个功能的乘积。 一种功能是系统的降级,与系统的参数无关,即 任何此类系统的共同点。 其次是对特定系统参数的指数依赖。 因此,对于一个人来说,有一个与衰老相关的共同功能,每个人或多或少都是一样的。 但是健康状况的恶化也与内部器官的状态有关,这对每个人来说都是不同的。

希望您现在对预测性维护有所了解。 我敢肯定,您将对这种技术最常用的机器学习方法有疑问。 我很乐意在评论中回答他们每个人。 如果您不仅对询问编写的内容感兴趣,还想做类似的事情,我们的CleverDATA团队将始终为有才华和热情的专业人员感到高兴。

有空缺吗? 当然可以!

Source: https://habr.com/ru/post/zh-CN447190/


All Articles