2020年4月17日

Thingworx Analytics介绍之二:TTF预测实操


本文介绍一个IoT领域较为关注的应用:机器失效时间(Time To Failure)预测。
系统环境:Thingworx Platform 8.5Analytics Server 8.5Analytics Extension 8.5

1步,配置Analytics参数。
点击Analytics图标>>Analytics Manager>>Analysis Providers,新建一个Analysis ProviderConnector类型为TW.AnalysisServices.AnalyticsServer.AnalyticsServerConnector
点击Analytics Builder>>Setting,设置Analytics Server

2步,创建分析数据。
首先要准备好CSV格式的数据文件和JSON格式的数据类型说明文件。
CSV文件的第一行是表头,应该和JSON文件保持一致。
JSON文件结构可参考:
---------------------------------------------------
[
       {
              "fieldName": "s2",
              "values": null,
              "range": null,
              "dataType": "DOUBLE",
              "opType": "CONTINUOUS",
              "timeSamplingInterval": null,
              "isStatic": false
       },
       {
              "fieldName": "s3",
              "values": null,
              "range": null,
              "dataType": "DOUBLE",
              "opType": "CONTINUOUS",
              "timeSamplingInterval": null,
              "isStatic": false
       }
]
---------------------------------------------------
CSV文件已包含以下信息:
1)     用于判定结果的字段(goalField)
2)     关键参数字段。
3)     用于过滤数据的字段,如建立一个字段record_purpose,部分数据值为training,部分数据值为scoring,前者用于训练模型,后者用于分析模型的准确性。
4)    如果是time series data,还要建立两个字段,分别表示训练次数和训练内周期。
按照原理来说,机器TTFtime series建模更为合适,但是由于Thingworx本身机制的原因(利用Value Stream存储time series data,但是无法保证事务性),本人实测在8.5试用版无法适用,因此本文不采用time series
本文采用了一个NASA的公开数据集进行训练,下载地址:
https://c3.nasa.gov/dashlink/resources/139/
数据准备好以后,点击Analytics Builder>>Data>>New..
选择CSV文件和JSON文件,然后在“Review uploaded metadata”,以便再次核对数据类型。
一般的参数字段类型为Continuous,结果字段是ContinuousBoolean,本例中为Continuous
用于筛选的字段,其类型为Informational
点击Create Dataset
创建成功后,新建Data会显示在Datasets列表中。
选择新建的Dataset>>View>>Filters>>New,可创建过滤器。

3步,创建机器学习模型。
点击Analytics Builder>>Models>>New
选择Dataset,然后选择结果字段和过滤器,此外还可以选择排除字段,以减少干扰提高效率:


点击Advanced Model Configuration,可配置高级参数:


在机器学习中,通常我们将数据按用途分成训练集、验证集、测试集3大部分,比例可设置为60%20%20%
在此步骤,我们可利用过滤器排除测试用数据,然后可用Validation Holdout %来定义验证数据比例,默认为20%
Learning Techniques包含了可用的机器学习算法,我们还可以手动添加算法及修改参数。
我的建议是用多种算法各训练一遍,然后用测试数据来分析各算法的匹配度。
同一种算法还可以利用不同的参数进行多次训练。
Ensemble Technique是多种算法的混合方式,也可以用多种方式进行尝试。
我的训练方法:


 其实机器学习工程师或者所谓数据科学家的工作,很大一部分就在于:选择算法、调整参数、分析结果。
点击Submit就可以创建模型。
通常小型的数据集,训练时间比较短,但是time series data训练时间会长得多,此外数据集数据量越大则训练时间越长。
成功后在Analytics Builder>>Models可以看到新建的模型。
选中模型后点击Publish,成功后可以在Analytics Manager>>Analytics Models看到此模型,并自动进入测试页面。

4步,模型初步测试。
模型在创建时会自动进入测试页面,我们也可以Analytics Manager>>Analysis Models选中模型后,点击View>>Test进入测试页面:


causalTechnique一般设为FULL_RANGE
goalField即用于判定结果的字段。
然后输入各参数的值,之后点击Add Row。如果是time series data,则要输入多个值。
选择第一行数据,点击Set Parent Row,然后点击Submit Job
系统会根据输入数据,结合算法模型,计算得到结果。
约数秒后,可得到结果。
在下方的Results Data Shape中,选择AnalyticsServerConnector.xxx,点击Refresh Job,可以看到结果。
此项计算的更多信息可以在Analysis Jobs中查看。

5步,设置模型自动计算。
模型建立以后,我们可能还会继续观察一段时间,用预测结果比较实际值,从而对模型的准确度有更深入的认识。
以本文的NASA数据集为例,我们可利用其部分数据进行比较,具体如下。
首先建立一个DataTable,把CSV中的数据导入。
然后建立一个Thing,把各参数值作为Property进行更新。
然后建立一个Timer,把测试数据定期读入数据,用这些值进行测试。
点击Analytics Manager>>Analysis Models,把新建的Model Enable
点击Analysis Events>>NewSource Type=ThingSource为新建的ThingEvent=DataChageProperty设置为触发字段。
保存后在Analysis Events中选择新建的Event,点击Map Data>>Inputs MappingSource Type=Thing,把模型的参数与Thing Property进行绑定。
Tips:模型的参数以_开头,此外还有causalTechniquegoalField,如果我们在Thing中定义了这些参数,则可以在此步骤中使用Map All,则系统会自动映射名称匹配的参数。
然后点击Results Mapping,把测试结果和Thing Property绑定。此处需注意,由系统算法计算得到的值会得到更新,但是不会记录到Value Stream中,所以我们必须另建一个Property,通过Service把结果同步过来。
配置好Event后,系统会自动监控触发条件,一旦符合则调用Analytics API自动计算得到结果,然后予以输出。
手动测试的结果和Event的结果都可以在Analysis Jobs中查看。

下图是我做TTF Demo Mashup界面:


下图是不同模型的分析对比:


下图罗列几个坑点和注意事项:
1)    由于time series data采用Value Stream记录历史值,而Value Stream无法保证多个Property同时更新时的事务性,因此不能利用Event来进行实时预测。
2)    由于Analytics采用同步执行,如果要进行多个模型的对比分析,在设置Event的触发条件时,务必要保证多个Event不能在同一个时间点发生,不然会有大量的WAITING JOB。我的办法是,复制得到多个触发字段,然后用PAUSE指令进行延时。
3)    如果原始数据有多个参数,可先建一个小模型,只选用2个字段,然后进行训练、测试,用此模型发现建模中的问题,优化Data TableThingTimer的逻辑,没有问题后再将模型扩展到更多字段,这样会顺利很多。
4)    慎用Timer,操作不慎会导致Value Stream数据大量增加,或者产生大量WAITING JOBWAITING JOB太多会堵塞分析和预测。可以通过Resource TW.AnalysisServices.JobManagementServicesAPI.DeleteJobs强制删除Jobs,可以通过Data Table TW.AnalysisServices.AnalysisDataTable.GetDataTableEntryCounts查看Job数量。



2020年4月16日

Thingworx Analytics介绍之一:Analytics功能及模块


最近花了不少时间学习Thingworx Analytics,踩了很多坑,也积累了一些心得,特撰文记录。
本文是第一篇,介绍Analytics的功能和模块。
第二篇文章将以实例说明怎样通过Analytics预测机器失效时间(Time to Failure)
2015年,PTC收购了机器学习公司ColdLight,将其产品整合进Thingworx,并命名为Thingworx Analytics

首先我们看看此产品有哪些功能。
PTC Thingworx Analytics帮助文档列出了这些功能:
• Explanatory Analytics: 解释型分析,比如甄别信号(Signal)是否有价值。
• Descriptive Analytics:描述型分析,如平均值、中值、标准偏差等的计算。
• Model Generation:预测型模型的建模和学习,包含了几个流行的机器学习算法。
• Predictive Scoring:预测计分,根据学习好的模型和新的参数值,以预测结果。
• Prescriptive Scoring:权重计分,调整模型参数的权重以修改分值。
• Confidence Models:信心模型,将预测值转换成定义区间的概率。
• Anomaly Detection:异常检测,比较信号的上下界,以过滤出异常信号。
• Time Series Predictions:时间序列模型的预测。有些信号是与其时间序列相关的,每次在预测时,除了检查当前值,还会分析之前的若干时间序列的参数值。但是我发现8.5版本此功能有BUG,因为Analytics是根据参数在ValueStream中记录的值来进行分析的,但是如果一个模型有多个参数且记录在同一个ValueStream的话,数据在更新时,ValueStream无法保证这些值在同一条记录中被更新。
• Learners and Ensemble Techniques:机器学习算法及混合方法。

Analytics内置了以下机器学习算法:
• Linear Regression
• Logistic Regression
• Decision Tree
• Neural Network
• Random Forest
• Gradient Boost

下面我们再来看一下Analytics组成模块。
Analytics3个安装模块:Analytics ServerAnalytics PlatformAnalytics Client(extension)
Analytics Server是独立与Thingworx Plantform的模块,但是有部分组件也会安装进Thingworx
Analytics Server安装完成以后,会在操作系统中增加以下Service
twas-analytics-worker-1
twas-analytics-worker-2
twas-analytics-worker-3
twas-async-ms
twas-sync-ms
twas-twx-adapter
twas-zookeeper
并且在Thingworx中增加以下对象:
StatisticalCalculationMicroserver,提供了多个SPC计算的Service
StatisticalMonitoringMicroserver,提供了多个SPC数据分布分析的Service
多个名称包含AnalyticsServerThing,可以在Monitoring >> Remote Things看到,提供了Thingworx Server API
前缀为TW.AnalysisServices的多个DataTable,是配置Analytics的数据库。
前缀为TW.AnalysisServices的多个Resource,对应于Analytics Builder的一些功能,如TW.AnalysisServices.AnalysisHelperFunctions可用于Job的统计、删除等。

Analytics Platform是一个独立模块,不安装也不影响学习及预测,我估计是用于分布式运算、异步运算等。
Analytics Client可以单独作为extension安装,安装以后在Thingworx中会增加一个Analytics图标,提供了一个Analytics模块的前台界面。
Analytics模块的菜单分为BuilderManager两个,其中Builder主要用于建模,Manager用于后台自动处理。
具体操作我会在第二篇中说明。



2020年1月6日

介绍几个非典型编程语言




1.     PLC梯形图
在通常的编程语言中,我们用IF…THEN…ELSE,以及AND…OR…等文本语法来进行逻辑处理。
比如下面的SCL语句表达了变量11121314的关系:

用梯形图来表达的话是这样的:

我们看到梯形图表达了一个简单而又典型的电路串、并联结构,非常容易理解。
此外,梯形图中还有专用于信号触发的上升沿、下降沿,如:

对应的SCL语句:


2.     LabView
顾名思义,LabView语言是用于实验室仪器管理的。
在制造领域,常用于测试工艺的执行和数据采集。

如上图是一个警告灯的逻辑程序。
我们可以看到,其程序是由一系列的仪器图标串起来的,其逻辑对应于设备信号的输入、输出、转换过程。
和梯形图一样,LabView的源代码也是二进制文件。

3.     Octave/MatLab
在大数据、人工智能时代,OctaveMatLab这两门语言也出镜得越来越多了,它们提供了一系列强大的数学工具,能够高效率地进行矩阵计算。
比如线性回归的公式为:

用传统编程语言计算的话,需要进行成千上万次迭代。
Octave只需一行代码:
theta = pinv(x’*x)*x’*y
此外,Octave也提供了强大方便的绘图工具,它可以直接通过三角函数绘制波形图,也可以直接引用矩阵数据绘制数据分布图,是数据分析的强大工具。


2019年12月16日

在Thingworx中绘制正态分布图


SPC分析中,正态分布图是使用较为频繁的图表。
本文介绍一下如何在Thingworx中绘制正态分布图。


如图所示,我们可以看到图表中包含:数据的分布情况(每个数据区间的数量),和参考正态分布曲线。

1.     绘图工具和注意项
我们可以用Label Chart工具来绘制图形。
注意不要选中“SingleDataSource”,这样我们可以引用两个数据源。
1个数据源引用自数据分布集,SeriesType1=Bar
2个数据源引用自正态分布集,SeriesType2=Line
两个数据集的X值应一致。
启用“YAxisAutoscale”,因为两个数据集的Y值是不一样的,自动缩放后数据会落到相同区间。

2.     数据分布计算
我们可以根据平均值和标准差来计算数据的分布。
标准差(σ)的计算可参考我的文章《Thingworx中的SPC计算工具》。
为了简化计算,我们选取常用的±3σ这个范围。
计算很简单,但是要注意一点:柱状图的X轴显示的是引用范围的起始值,但是我们在正态分布图中参考的是范围平均值,因此在最后,我们要把柱状图X轴加上0.5σ
下面是代码:
------------------------------------------------------------------------------
// To generate Histogram dataset for SPC
// Developed by Tallrain, Dec 2019
// Input
//    avg = Average
//    sd  = Standard Deviation
//    spcTable = SPC data, with field of SPC1 for data value
// Output
//    infoTable, with fields of value and count

var bar1 = avg-3*sd;
var bar2 = avg-2*sd;
var bar3 = avg-1*sd;
var bar4 = avg;
var bar5 = avg+1*sd;
var bar6 = avg+2*sd;
var bar7 = avg+3*sd;

var v1 = 0;
var v2 = 0;
var v3 = 0;
var v4 = 0;
var v5 = 0;
var v6 = 0;
//var v7 = 0;

var tableLength = spcTable.rows.length;
for (var x=0; x < tableLength; x++) {
    var row = spcTable.rows[x];
    var v = row.SPC1;
   if(v>=bar1 && v
   if(v>=bar2 && v
   if(v>=bar3 && v
   if(v>=bar4 && v
   if(v>=bar5 && v
   if(v>=bar6 && v  
}

bar1 = bar1 + sd*0.5;
bar2 = bar2 + sd*0.5;
bar3 = bar3 + sd*0.5;
bar4 = bar4 + sd*0.5;
bar5 = bar5 + sd*0.5;
bar6 = bar6 + sd*0.5;
bar7 = bar7 + sd*0.5;

// Output data
var params = {
    infoTableName : "InfoTable",
    dataShapeName : "DS_SPC_Histogram"
};

//CreateInfoTableFromDataShape(infoTableName:STRING("InfoTable"), dataShapeName:STRING):INFOTABLE(DS_SPC_CPK_OUT)
var spcOut = Resources["InfoTableFunctions"].CreateInfoTableFromDataShape(params);
spcOut.AddRow({value:bar1, count:v1});
spcOut.AddRow({value:bar2, count:v2});
spcOut.AddRow({value:bar3, count:v3});
spcOut.AddRow({value:bar4, count:v4});
spcOut.AddRow({value:bar5, count:v5});
spcOut.AddRow({value:bar6, count:v6});
//spcOut.AddRow({value:bar7, count:0});

var result = spcOut;
------------------------------------------------------------------------------

3.     正态分布计算
算法可参考WIKI
在这里为了和柱状图契合,我们按照每0.5σ为基础单位进行计算。
要说明的是,正态分布公式的值并不是概率,X区间和曲线相交的面积才是概率。
具体的值和平均值、标准差相关,所以在绘制图表时要允许Y轴自动绽放,这样两个图才能匹配。
下面是代码:
------------------------------------------------------------------------------
// Generate SPC Normal Distribution
// Developed by Tallrain, Nov 2019
// Input
//    u = Average
//    sd = Standard Deviation
// Output
//    infoTable, with field of x and np

// Create temp InfoTable for output
var params = {
    infoTableName : "InfoTable",
    dataShapeName : "DS_Normal_Distrib"
};

// CreateInfoTableFromDataShape(infoTableName:STRING("InfoTable"), dataShapeName:STRING):INFOTABLE(DS_Normal_Distrib)
var tempInfoTable = Resources["InfoTableFunctions"].CreateInfoTableFromDataShape(params);

// calculate normal distribution data
var e = Math.E; //2.71828182846;
var pi = Math.PI; // 3.14159265359;
for (var i=1; i <= 13; i++) {
    var x = (i-7)/2;
    x = u + x*sd;   
    var dividend = Math.pow(e, 0-((x-u)*(x-u)/(2*sd*sd)));
    var divisor = sd*Math.sqrt(2*pi);
    var np = dividend/divisor;
    //const dividend = Math.E ** -((value - this.mean) ** 2 / (2 * this.standardDeviation ** 2));
    //const divisor = this.standardDeviation * Math.sqrt(2 * Math.PI);
    //var np = 0-(x-u)*(x-u)/(2*sd*sd);
    //logger.info('np='+np);
    //np = Math.pow(e,np);
    //var np2 = sd*Math.sqrt(2*pi);
    //np = np/np2;
    //logger.info('np='+np);
    tempInfoTable.AddRow({x:x, np:np});   
}

var result = tempInfoTable;
//var result = Math.pow(3,2);
------------------------------------------------------------------------------


2019年12月9日

Thingworx的非官方扩展


我们知道PTC提供了官方商城: https://marketplace.ptc.com/
我们从官方商城可以下载很多常用的扩展,如JDBC Connector Extension、PTC ThingWorx-to-Windchill Connector、QRCode等。
这些扩展通常由PTC官方提供,也有一些是第三方的应用或解决方案如Callisto Digital Work Instructions。
但是官方商城的扩展很少。
PTC技术人员在论坛上推荐了另一个非官方扩展下载地址: https://github.com/ptc-iot-sharing
这是发布在github上的,大部分是开源包,基于Java或JavaScript开发,如有很多基于D3引擎开发的图形化数据展示工具。
大家可以尝试一下,当然稳定性方面可能和官方扩展有所差距。

2019年11月13日

Thingworx中的SPC计算工具


Thingworx提供了两个SPC计算工具: StatisticalCalculationThingShape,和StatisticalMonitoringThingShape,但前提条件是需要DescriptiveAnalytics平台。
大致步骤是:
1. 购买安装DescriptiveAnalytics。
2. 建立Thing,配置Data Shape和Value Stream,设置数值属性logged。
3. 调用StatisticalCalculationThingShape.QueryTimedValuesForProperty生成TimedValues类型的InfoTable。
4. 调用StatisticalCalculationThingShape.CalculateMeanValue等函数生成具体的SPC数值。
这种方式适用于工艺参数采集,即Thing和Property均已事先定义的情况下。

如果不采用DescriptiveAnalytics平台,或者说数据不适合用Value Stream记录(比如直接从拧紧枪数据库查询数据),那么就需要自己计算SPC了。
具体步骤为:
1. 经由数据库查询等方式生成InfoTable类型的数据集。
2. 调用infotable for loop方法,逐行读取并分析数据,计算最大值、最小值、平均值。
3. 利用数组暂存数据,然后读取数组,计算标准方差。不用数组的话,也可以将InfoTable重新读取一次。
4. 输出InfoTable类型的数据集。
以下是参考代码:
------------------------------------------------
// Get USL&LSL
var USL = spcTable.rows[0].USL;
var LSL = spcTable.rows[0].LSL;
//logger.info("USL="+USL);
//logger.info("LSL="+LSL);

// Get Sum
var sum = 0.0;
var spc1 = []; // array of spc1
var tableLength = spcTable.rows.length;
//logger.info("tableLength="+tableLength);
for (var x=0; x < tableLength; x++) {
    var row = spcTable.rows[x];
    spc1[x] = row.SPC1;
    //logger.info("spc1="+row.SPC1);
    sum = sum + row.SPC1;
}
//logger.info("x="+x);
//logger.info("sum="+sum);
//var result = sum;

// Get Average
var u = sum / tableLength;
//logger.info("u="+u);

// Get SD
var sd = 0;
for (x=0; x < tableLength; x++) {
    sd = sd + (spc1[x]-u) * (spc1[x]-u);
}
sd = sd/(tableLength-1);
sd = Math.sqrt(sd);
//logger.info("sd="+sd);

// Get CPU
var cpu = (USL-u)/(3*sd);
//logger.info("cpu="+cpu);

// Get CPL
var cpl = (u-LSL)/(3*sd);
//logger.info("cpl="+cpl);

// Get CPK
var cpk = 0;
if(cpu < cpl) {
cpk = cpu;
} else {
cpk = cpl;
}
//logger.info("cpk="+cpk);

// Output data
var params = {
    infoTableName : "InfoTable",
    dataShapeName : "DS_SPC_CPK_OUT"
};

// CreateInfoTableFromDataShape(infoTableName:STRING("InfoTable"), dataShapeName:STRING):INFOTABLE(DS_SPC_CPK_OUT)
var spcOut = Resources["InfoTableFunctions"].CreateInfoTableFromDataShape(params);
spcOut.AddRow({Sum:sum, Avg:u, SD:sd, USL:USL, LSL:LSL, CPU:cpu, CPL:cpl, CPK:cpk, Qty:tableLength});

var result = spcOut;
------------------------------------------------

2019年11月4日

关于SQL SERVER的两个备忘



最近在用SQL SERVER做数据的后台自动处理,然后利用Thingworx展现。
这其中碰到两个问题,觉得较有代表性,在此做个备忘。

1. 关于游标内调用其它游标
我之前做过很多项目采用ORACLE数据库,其动态游标、物化视图、数组的功能非常NB,给我留下了深刻的印象。
但是我没想到在使用SQL SERVER游标时却踩到了坑。
SQL SERVER有一个全局变量@@FETCH_STATUS用来表示游标的状态,0表示尚未结束,-1表示已结束。
但是关键@@FETCH_STATUS是全局变量,这就意味着当第一个游标尚未结束时,当第二个游标的状态=-1时,也会将第一个游标强行结束。
解决的办法是用一个本地的变量,把@@FETCH_STATUS的值赋给此变量后再进行检查。
但是此办法逻辑上并不完备,如果有大量的并发,则可能会造成冲突。
因此如果需要配置后台任务,应该尽可能在时间上分散开来,以避免并发。
下面是相关的参考代码:

---------------------------------------------------------------------------
-- Normally, we use system variable @@FETCH_STATUS to check Cursor loop
DECLARE myCursor CURSOR FOR
SELECT fields FROM Table;
OPEN myCursor;
FETCH NEXT FROM myCursor INTO @myVar;
WHILE @@FETCH_STATUS = 0
BEGIN
  --DO somthing;
END
CLOSE myCursor;
DEALLOCATE myCursor;
 
-- But @@FETCH_STATUS is global variable
-- So if we use another Cursor within the loop, when inner loop is completed, @@FETCH_STATUS = -1, so the outer loop will also be closed.
-- Fixing method is to use local variable instead of @@FETCH_STATUS
DECLARE myCursor CURSOR FOR
SELECT fields FROM Table;
OPEN myCursor;
FETCH NEXT FROM myCursor INTO @myVar;
SET @Outer_loop = @@FETCH_STATUS;
WHILE @Outer_loop = 0
BEGIN
  --inner loop here
  DECLARE myCursor2 CURSOR FOR
  SELECT fields FROM Table2;
  OPEN myCursor2;
  FETCH NEXT FROM myCursor2 INTO @myVar2;
  SET @Inner_loop = @@FETCH_STATUS;
  WHILE @Inner_loop = 0
  BEGIN
    --Inner loop function
FETCH NEXT FROM myCursor2 INTO @myVar2;
    SET @Inner_loop = @@FETCH_STATUS;
  END
  CLOSE myCursor2;
  DEALLOCATE myCursor2;
  FETCH NEXT FROM myCursor INTO @myVar;
  SET @Outer_loop = @@FETCH_STATUS; 
END
CLOSE myCursor;
DEALLOCATE myCursor;
---------------------------------------------------------------------------

2. 关于关联字符集
由于数据库权限方面的限制,我在一个项目中需要对两个数据库中的表进行关联查询,然后碰到了字符集不匹配的报错。
具体的情况是这样的: 数据库DB1采用了GB字符集,数据库DB2采用了LATIN字符集,两个库的表T1和T2通过step_id进行JOIN,而step_id的数据类型是NVARCHAR,因此JOIN失败。
这是因为对于字符串类对象,虽然表示同样的值,当采用不同的字符集时,其存储时的值是不同的。
这个CASE给我的启示是,任何可能用于JOIN或外部引用的对象,要尽可能采用整型数据类型。
事实上,对于数据库来说,整型和日期型数据的检索速度是最快的,当有JOIN、分组查询、外部引用时,要尽可能使用整型数据。