在 InfoSphere Streams 中集成 SPSS Model Scoring,第 2 部分: 使用泛型运算符

在 InfoSphere Streams 中集成 SPSS Model Scoring” 系列文章的第 2 部分中,将介绍如何创建泛型运算符,从而在 InfoSphere® Streams 应用程序中执行 IBM® SPSS Modeler 预测模型。第 2 部分建立在第 1 部分生成的非泛型运算符基础之上,第 1 部分中编写并使用了一个 InfoSphere Streams 运算符,在 InfoSphere Streams 应用程序中利用 IBM SPSS Modeler Solution Publisher Runtime 库 API 执行预测模型。

Mike Koranda, InfoSphere Streams 发布经理, IBM Global Business Services

Mike KorandaMike Koranda 是 IBM 的 Software Group 的一名高级技术人员,已经在 IBM 工作了 30 多年。最近 6 年,他一直在从事 InfoSphere Streams 产品的开发工作。



2012 年 1 月 30 日

开始之前

免费下载:IBM® InfoSphere Streams V2 试用版
下载更多的 IBM 软件试用版,并加入 IBM 软件下载与技术交流群组,参与在线交流。

本教程将介绍如何创建一个可在 Streams 应用程序中使用并执行 SPSS 预测模型的泛型运算符。本文还将提供一个可直接用于适当 SPSS 模型的运算符样例,以及一个 Streams 应用程序,用该应用程序来演示运算符的用法。

关于本系列

InfoSphere Streams 是一个能对不断变化的数据进行实时分析的平台。IBM SPSS 系列产品提供了建立预测分析模型的功能。“在 InfoSphere Streams 中集成 SPSS Model Scoring” 系列可供需要在实时评分环境中利用功能强大的预测模型的 Streams 开发人员参考。

关于本教程

本教程扩展了在 InfoSphere Streams 中集成 SPSS Model Scoring,第 1 部分:从 InfoSphere Streams 运算符调用 Solution Publisher 中生成的非泛型运算符,展示了一项非常灵活的技术,但需要一些 C++ 编程技巧进行定制。

目标

在本教程中,您将学习如何扩展该非泛型运算器,以便使用预测模型的 XML 元数据,可以在 Streams 中实现定制,无需任何 C++ 技能。

先决条件

本教程面向具有 Streams 编程语言和 C++ 技能的Streams 组件开发人员和应用程序编程人员阅读。参考本教程,或查看并执行其中的样例,都可以了解此技术。如果要执行样例,则需要对 UNIX® 命令行 shell 以及 Streams 编程知识有一些了解。

系统要求

要运行此样例,需要配备一台装有 Red Hat Enterprise Linux® 计算机,并安装 InfoSphere Streams V2.0 或其更高版本、 IBM SPSS Modeler Solution Publisher 14.2 fixpack 1 和 Solution Publisher hot fix(预计于 2011 年 10 月 14 日发布)。


概览

简介

本教程将演示如何创建一个泛型运算符,以便在 InfoSphere Streams 应用程序中执行 SPSS 预测模型。本文建立在第 1 部分生成的非泛型运算符基础之上,在第 1 部分中编写和使用了 InfoSphere Streams 运算符,在 InfoSphere Streams 应用程序中利用 IBM SPSS Modeler Solution Publisher Runtime 库 API 执行预测模型。

第 1 部分概述

在第 1 部分中,我们开发了一个运算符,它将特定的预测模型打包,只能在样例所示的特定模式下使用该运算符。回想一下,我们演示了如何修改非泛型运算符,以便将它用于不同模型和模式,但是,用户至少需要掌握一些 C++ 编程技巧,以便进行必要的调整。创建一个可以自动调整自身以适应不同模型的泛型运算符,这样就可以完全由 Streams 应用程序程序员来实现集成,从而减少为每个模型创建单独运算符所需的工作量和技能要求。

角色和术语

第 1 部分介绍了角色和术语,这里不再赘述。此处重点关注的是 Streams 组件开发人员角色,以及如何编写一个泛型运算符,让 Streams 应用程序开发人员按需使用它来执行 SPSS 预测模型。如果需要通过了解其他角色来了解整体解决方案的工作和交流内容,请参阅第 1 部分。

数据分析员与 Streams 组件开发人员之间的协作

回想一下 在 InfoSphere Streams 中集成 SPSS Model Scoring,第 1 部分:从 InfoSphere Streams 运算符调用 Solution Publisher,为了编写 Streams 运算符,Streams 组件开发人员需要数据分析员提供某些关于预测模型输入和输出的信息。运算符开发人员尤其需要以下信息:

  • Solution Publisher 安装位置
  • 发布过程中生成的 .pim 和 .par 文件
  • 输入源节点键名称。这些内容可在 XML 段落中找到:
    <inputDataSources>
        <inputDataSource name="file0" type="Delimited">
    请注意:为了简便起见,样例中只支持一个输入源,而实际上对此没有技术限制。
  • 可以在 <inputDataSource> 标记中找到输入字段的名称、存储位置及其顺序。
    清单 1. <inputDataSource> 标记
    <fields>
      <field storage="string" type="discrete">
        <name>sex</name>
      </field>
     <field storage="integer" type="range">
      <name>income</name>
     </field>
    </fields>
  • 输出节点或终止节点键名称。
    <outputDataSources>
        <outputDataSource name="file3" type="Delimited">
    请注意:为了简便起见,样例中只支持一个输出源,而实际上对此没有技术限制。
  • 可以在 <outputDataSource> 标记中找到输出字段的名称、存储位置及其顺序。
    清单 2. <outputDataSource> 标记
    <fields>
     <field storage="string" type="discrete">
       <name>sex</name>
     </field>
     <field storage="integer" type="range">
      <name>income</name>
     </field>
     <field storage="string" type="flag">
      <name>$C-beer_beans_pizza</name>
    ...
     </field>
     <field storage="real" type="range">
      <name>$CC-beer_beans_pizza</name>
    ...
     </field>
    </fields>

回想一下,为了调整运算符样例以用于不同的输入元组/模型,您需要在以下位置进行调整:

  1. _h.cgt 文件
    调整输出结构
  2. _cpp.cgt 文件
    1. 下一条返回记录 ... —
      1. 调整输出结构
    2. 构造函数 ...
      1. 调整 .pim 和 .par 文件的名称和位置
      2. 调整输入和输出文件标记
      3. 调整输入字段指针数组大小
    3. 过程
      1. 调整加载输入结构代码
      2. 调整加载输出元组代码

下一步,我们将描述泛型运算符的设计,它使用元数据和运算符的参数来自动生成非泛型运算符需要手工调整的代码。


设计运算符

设计 Streams 泛型评分运算符

在概念层,泛型运算符要做的是向 Streams 应用程序开发人员提供参数,从而使他们可以将运算符用于包含各种输入输出的预测模型。在这一章中,我们将描述泛型运算符所提供的参数以及生成这组参数的设计决策。

泛型运算符的设计先从持续支持一个输入端口和一个输出端口开始。

现在来回想一下,非泛型运算符中的 .pim 和 .par 文件名已在 .cgt 文件中硬编码。为了能让泛型运算符接收所有的用户指定模型模型文件,需要将这些传递给运算符。使用参数可以使其规范与非泛型运算符处理 SPInstall 位置的方式类似。

前面曾提到过,Streams 组件开发人员构建非泛型运算符所需的许多信息都是通过检查预测模型的 XML 元数据文件获得的。当编译使用泛型运算符的应用程序时,需要通过程序读取此文件,动态生成所需的运算符。我们将使用与 .pim 和 .par 文件用法类似的参数,以便可以使用 XML 源文件规范。

这让 Streams 应用程序可以使用泛型运算符,这是实现能够指定使用哪个输入元组数据来填充模型输入字段功能的关键一步。在这个简单的泛型模型中,我们对此进行了硬编码,并要求元组的属性与模型输入字段的格式兼容。对于泛型运算符,我们需要允许应用程序开发人员指定如何用适当的输入数据填充输入字段。为了实现此功能,我们定义了两个参数,这两个参数都是列表。第一个列表包含 SPL 表达式,表示从输入元组数据中获得的用来填充模型的输入字段的输入值。第二个列表表示第一个列表中对应的表达式会填充哪个模型输入字段。

通过允许使用 SPL 表达式,在指定使用哪个数据时有了更大的灵活性。在最简单的情况下,该表达式应该是格式正确的元组属性,也就是说,应该与非泛型运算符中使用的一样。通过允许使用 SPL 表达式,现在可以从几个属性生成一个复杂的表达式,从而提供所需的正确输入数据。它可以是从可用的输入元组数据到模型所需的对应数据类型的简单转换、使用若干属性来获得所需输入字段值的复杂表达式,也可以是一个文本值,此时模型需要的数据不用再输入元组中,默认值就已经足够用了。我们原本可以通过使用一个列表获得所需数据,并要求应用程序开发人员保证表达式按照模型需要的顺序列出,我们认为这样做容易出错,并让应用程序开发人员提供第二个参数,它是映射到已有字段名称的显式列表。第二个列表参数可以实现我们的目标。

我们还需要一组同样的信息,以便从模型输出字段映射到输出元组。我们认为最自然的 Streams 实现是通过 SPL 输出子句来指定,并在运算符的输出端口使用自定义输出函数。如果这么做,那么可以让应用程序开发人员选择使用哪些输出字段,忽略哪些输出字段。我们既可以用一个基本的分配函数在输出元组中填充数据,也可以在模型不会为给定输入产生输出(丢失数据)的情况下使用默认值规范。输出值可以使用任何 SPL 表达式,因此可以通过现有属性或文本值构建。

现在已经完成设计,下一章将介绍如何在泛型运算符中实现该设计。


编写运算符

泛型评分运算符实现

现在已经完成了运算符的整体设计,接下来会看看实现该设计需要做的工作。首先是指定 Streams 开发人员将要使用的接口的机制。然后介绍在编译时用于生成所需代码的实现代码。

指定运算符的公共接口

为了编写泛型运算符,我们先来介绍以下 Streams 应用程序开发人员将要用到的接口,然后介绍 Streams 组件开发人员如何在泛型运算符实现中使用此信息。使用此运算符的 Streams 应用程序开发人员需要指定以下内容:

  1. 模型文件
  2. 模型 XML 元数据
  3. 输入映射
  4. 输出映射

指定模型文件

为了让 Streams 应用程序开发人员能将此运算符用于所有已发布的预测模型,需要允许使用已发布的 .pim 和 .par 文件的规范。通过添加参数,并提供在代码模板中使用这些参数的实现,才完成此任务。指定 .pim 和 .par 文件的参数被添加到运算符 XML 文件 SPSimple.xml 中的参数定义部分(参见 下载)。清单 3 显示了 .pim 文件的定义。.par 文件的定义与此类似。

清单 3. .pim 文件的定义
<parameter>
 <name>pimfile</name>
 <description></description>
 <optional>false</optional>
 <rewriteAllowed>true</rewriteAllowed>
 <expressionMode>Expression</expressionMode>
 <type>rstring</type>
 <cardinality>1</cardinality>
</parameter>

指定模型 XML 元数据

代码所需要的很多特征(字段名称、编号、顺序、类型)以及用于指示输入和输出字段的字段标记,都在预测模型的 XML 元数据中进行了描述。对于我们的泛型运算符,会使用一个参数来传递 XML 文件。

清单 4. XML 元数据
<parameter>
 <name>xmlfile</name>
 <description></description>
 <optional>false</optional>
 <rewriteAllowed>true</rewriteAllowed>
 <expressionMode>Expression</expressionMode>
 <type>rstring</type>
 <cardinality>1</cardinality>
</parameter>

指定输入映射

用来指定输入流属性和模型输入字段的参数被添加到操作符 XML 的参数定义部分,如下所示。

清单 5. 操作符 XML 的参数定义部分
<parameter>
 <name>modelFields</name>
 <description docHref="" sampleUri="">a list of strings naming the model 
       input fields</description>
 <optional>false</optional>
 <rewriteAllowed>false</rewriteAllowed>
 <expressionMode>Expression</expressionMode>
 <type>rstring</type>
 <cardinality>-1</cardinality>
</parameter>
<parameter>
 <name>streamAttributes</name>
 <description docHref="" sampleUri="">a list of expressions that define the data
        derived from stream attributes to pass on each model input field.</description>
 <optional>false</optional>
 <rewriteAllowed>false</rewriteAllowed>
 <expressionMode>Expression</expressionMode>
 <cardinality>-1</cardinality>
</parameter>

指定输出映射

输出字段到输出元组属性的映射在 SPL 运算符调用的输出子句中进行。我们需要定义一些自定义函数来支持此项操作。我们定义了三个函数:一个默认函数不使用模型输出,两个扩展函数,提取模型输出并填充模型属性。第一个填充方法假设如果模型执行时不生成此字段的值,输出元组将包含此属性类型的默认值(整数值 0)。第二个方法允许使用字段和其他表示 SPL 表达式的值的规范来生成默认值。清单 6 显示输出函数规范。

清单 6. 输出函数规范
<customOutputFunctions>
 <customOutputFunction>
  <name>SPOutputs</name>
  <function>
   <description sampleUri="">Return the argument unchanged</description>
   <prototype><any T> T defaultFromModel(T)</prototype>
  </function>
  <function>
   <description sampleUri="">return the field from the model output buffer</description>
   <prototype><any T> T fromModel(rstring)</prototype>
  </function>
  <function>
   <description sampleUri="">return the field from the model output buffer if it 
         doesn't exist set a default</description>
   <prototype><any T> T fromModel(rstring, T)</prototype>
  </function>
 </customOutputFunction>
</customOutputFunctions>

实现

现在接口已经指定,我们将讨论 Streams 组件开发人员如何在泛型运算符实现中使用此信息。泛型运算符实现将在以下范围内进行:

  1. 常用 — 在头文件代码生成模板和 CPP 代码生成模板中使用的 Perl 代码
  2. 头文件 — 头文件代码生成模板实现区
  3. 函数 — 传递到 Solution Publisher 接口的函数的 CPP 代码生成模板
  4. 构造函数 — 与 Solution Publisher 接口相关的运算符的构造函数的 CPP 代码生成模板实现
  5. 过程 — 当输入元组到达运算符输入端口时,在过程方法中调用的代码的 CPP 代码生成模板实现

模型 XML 元数据的常见用法

由于从 XML 元数据中获取的信息将用于 H 和 CPP 代码生成模板中,因此我实现一个名为 SP_Common.pm 的常用例程来验证和解析 XML 元数据文档。SPCommon.pm 文件包含一个函数 ($infilename, $outfilename, @infields, @infieldtypes, $numinfields, @outfields, @outfieldtypes, $numoutfields) processModelFile($model)。它将验证模型文件参数值是否存在以及格式是否正确,并解析模型文件,找到 SP API 调用的输入文件名和输出文件名。他还会返回模型中定义的输入和输出字段的信息,尤其是各自的编号、字段名称和类型列表。在头文件和 CPP 模板处理过程中会调用此常用例程。以下是此常用函数中的 Perl 代码的摘录片段。

清单 7. Perl 代码摘录
# Get the value for the model parameter, the name of the model file.
# Note that if this is a relative path name, it is rooted in the "data"
# subdirectory of this directory, so we will need to compensate.
  my $parm_modelfile = 
      $model->getParameterByName('xmlfile')->getValueAt(0)->getSPLExpression();
  my $compile_modelpath;
  ($compile_modelpath) = ($parm_modelfile=~m/"(.*)"$/);
  # Absolute path case
  if (substr($compile_modelpath, 0, 1) ne "/") {
    $compile_modelpath = "data/" . $compile_modelpath;
  }

  # Convert to an absolute path
  $compile_modelpath = realpath($compile_modelpath);

  # Check that the model file is readable
  -f $compile_modelpath or 
      SPL::CodeGen::exitln("Model file ". $compile_modelpath. 
        " does not exist or is not a regular file");
  -r $compile_modelpath or 
      SPL::CodeGen::exitln("Model file ". $compile_modelpath. " is not readable");

  # Parse model file to get predictive model information
  my $xs1 = new XML::Simple;
  my $doc = XMLin($compile_modelpath, forcearray => [ qw(field) ], keyattr => [] );
  
  my $infilename; 
  $infilename = $doc->{inputDataSources}->{inputDataSource}->{name};
  
  ... lines omitted

首先,调整并验证文件名的参数值。然后解析 XML 内容,并将必需的信息提取成代码生成模板中 Perl 的可用格式。在以上的示例代码中,我们只演示了提取模型所需的第一个输入数据源的文件名。提取其他信息的代码与此类似,此处不再详述,具体描述可在 ZIP 文件中找到。

头文件模板代码 SPSimple_h.cgt 会在 SP_Common.pl 文件中调用此函数,以验证并填充模型变量,如下所示。

清单 8. SPCommon.pl 文件
# Process the model file, extract the field column names,
# and validate these with the operator parameter values.my ( $infilename_ref, 
 $outfilename_ref, 
 $infields_ref,
 $infieldtypes_ref, 
 $numinfields_ref, 
 $outfields_ref, 
 $outfieldtypes_ref, 
 $numoutfields_ref) = SP_Common::processModelFile($model);

 my $infilename = ${$infilename_ref};
 my $outfilename = ${$outfilename_ref};
 my @infields = @{$infields_ref};
 my @infieldtypes = @{$infieldtypes_ref};
 my $numinfields = ${$numinfields_ref};
 my @outfields = @{$outfields_ref};
 my @outfieldtypes = @{$outfieldtypes_ref};
 my $numoutfields = ${$numoutfields_ref};

这样就可以生成本地 Perl 变量,供代码生成模板使用方便。

头文件实现

在头文件模板中,我们使用与输出字段相关的值来创建输出字段所需的数据结构。

清单 9. 头文件实现
// Create a structure to match output row data 
typedef struct {
  void* next;
<%
  for(my $j=0; $j<$numoutfields; $j++) {
    if((@outfieldtypes[$j] eq 'long') or 
      (@outfieldtypes[$j] eq 'time') or 
      (@outfieldtypes[$j] eq 'integer') or 
      (@outfieldtypes[$j] eq 'date') or
      (@outfieldtypes[$j] eq 'timestamp')) {
         print "    long long $outfields[$j];\n";
    }
    elsif (@outfieldtypes[$j] eq 'real') {
      print "    double $outfields[$j];\n";
    }
    elsif (@outfieldtypes[$j] eq 'string') {
      print "    const char * $outfields[$j];\n";
    }
    else {
      SPL::CodeGen::errorln("Model output field $outfields[$j] of type:
        @outfieldtypes[$j] is not a valid type");  
    }
    print "    boolean _missing_$outfields[$j];\n";
  }
 %>	
} outBuffer;

函数实现

在 SPSimple_cpp.cgt 模板中,我们使用从 Solution Publisher 运行时调用的下一条记录返回函数中的输出字段信息,将返回数据的地址提供给运算符,让它可以将数据拷贝到输出元组中。

清单 10. 函数实现
<%
  for(my $j=0; $j<$numoutfields; $j++) {
    print "    if (row[$j]) {\n";
    if((@outfieldtypes[$j] eq 'long') or 
      (@outfieldtypes[$j] eq 'time') or 
      (@outfieldtypes[$j] eq 'integer') or 
      (@outfieldtypes[$j] eq 'date') or
      (@outfieldtypes[$j] eq 'timestamp')) {
        print "      obp->$outfields[$j] = *((long long *) row[$j]);\n";
    }
    elsif (@outfieldtypes[$j] eq 'real') {
      print "    obp->$outfields[$j] = *((double *) row[$j]);\n";
    }
    elsif (@outfieldtypes[$j] eq 'string') {
      print "    obp->$outfields[$j] = (const char *) row[$j];\n";
    }
    else {
      SPL::CodeGen::errorln("Unsupported output field type of:@outfieldtypes[$j]");
    }
    print "      obp->_missing_$outfields[$j] = false;\n";
    print "    } else {obp->_missing_$outfields[$j] = true;}\n";
  } 
%>

构造函数实现

CPP 模板代码 SPSimple_cpp.cgt 构造函数可以直接使用所提供的参数值(比如 pimfile),以及从常用例程处理 XML 元数据时获得的值。清单 11 显示了在 CPP 构造函数中如何直接使用参数信息。

清单 11. 构造函数实现
<%
   my $pimfileParam = $model->getParameterByName("pimfile");
   my $pimfile = $pimfileParam->getValueAt(0)->getCppExpression();
%>
rstring pimFile = <%=$pimfile%>;

然后,在构造函数中,会将 pimfile 值传递到一个 Solution Publisher API 调用中。

清单 12. 传递 pimfile
/* open the image */
     int res, status = EXIT_FAILURE;
     image_handle = 0;
     res = clemrtl_openImage(pimFile.c_str(),parFile.c_str(), &image_handle);
     if (res != CLEMRTL_OK) {
		status = EXIT_FAILURE;
		SPLLOG(L_ERROR, "Open Image Failed", "MPK");
		displayError(res, 0);
      }

与此类似,从常用例程中返回的值也在 Solution Publisher API 调用中使用。

清单 13. Solution Publisher API 调用
/* Get Input field count and types */
  	char* key="<%=$infilename%>";
  	SPLLOG(L_INFO, "About to get field count", "MPK");
  	res = clemrtl_getFieldCount(image_handle, key, &fld_cnt );

过程实现

过程方法中的实现主要有两个主要位置:

  1. 使用即将传入的元组值来填充 Solution Publisher 调用的字段结构,以执行预测模型所需的代码
  2. 获取模型执行结果,将合适的输出字段移动到运算符输出元组中,并将它们提交给输出端口的代码

处理输入元组

清单 14 显示了填充结构在输入元组中的使用情况。

清单 14. 处理输入元组
<%
 my $oport = $model->getOutputPortAt(0);
 #### first check for match of # infield in model vs modelFields param 
 #### vs steamAttributes param 
 my $modelFieldsParam = $model->getParameterByName("modelFields");
  	  my $modelFieldsSize = $modelFieldsParam->getNumberOfValues();
  my $streamAttributesParam = $model->getParameterByName("streamAttributes");
  	  my $streamAttributesSize = $streamAttributesParam->getNumberOfValues();
  	  
 if ($numinfields ne $streamAttributesSize) {
   SPL::CodeGen::errorln("Number of input ports required by model is: 
       $numinfields; Number of stream attributes provided is: 
       $streamAttributesSize",$oport->getSourceLocation());
 }
 if ($numinfields ne $modelFieldsSize) {
   SPL::CodeGen::errorln("Number of input ports required by model is: 
     $numinfields; Number of model fields provided is: 
     $modelFieldsSize",$oport->getSourceLocation());
 }
 
 #### build up a searchable array of model fields  
 
 my @modelFieldValues;
 for (my $i=0; $i <getValueAt($i);
   my $splexp = $exp->getSPLExpression();
   $splexp =~ s/^"(.*)"$/$1/;
   $modelFieldValues[$i] = $splexp;     
 } 
 
 #### go through each input field, find the modelField and it's corresponding 
 #### streamAttribute expression value, check for compatibility and 
 #### generate the proper storage definition and assignment to input buffer pointer.   
 for(my $j=0; $j<getSourceLocation());
   }
   #### find the stream attribute and type
   my $sexp =  $streamAttributesParam->getValueAt($index);
   my $scppexp = $sexp->getCppExpression();
   my $sadaptedcpp = SPL::CodeGen::adaptCppExpression($scppexp, "tp");
   my $scpptyp = $sexp->getCppType();
   my $ssplexp = $sexp->getSPLExpression();
   
   
   if((@infieldtypes[$j] eq 'long') or 
   	 (@infieldtypes[$j] eq 'time') or 
   	 (@infieldtypes[$j] eq 'integer') or 
   	 (@infieldtypes[$j] eq 'date') or
   	 (@infieldtypes[$j] eq 'timestamp')) {
   	   if ($scpptyp ne 'SPL::int64') {
            SPL::CodeGen::errorln("stream attribute $ssplexp of type: 
              $scpptyp is not compatible with model input field 
              $infields[$j] of type:$infieldtypes[$j]",$oport->getSourceLocation());
       }
	}
	elsif (@infieldtypes[$j] eq 'real') {
	  print "    // real check if compatible streamtype = $scpptyp \n";
	  if ($scpptyp ne 'SPL::float64') {
            SPL::CodeGen::errorln("stream attribute $ssplexp of type: 
              $scpptyp is not compatible with model input field $infields[$j] of type: 
              @infieldtypes[$j]",$oport->getSourceLocation());
      }
	}
	elsif (@infieldtypes[$j] eq 'string') {
	  print "    // string check if compatible streamtype = $scpptyp \n";
	  if ($scpptyp ne 'SPL::rstring') { 
            SPL::CodeGen::errorln("stream attribute $ssplexp of type: 
              $scpptyp is not compatible with model input field $infields[$j] of type: 
              @infieldtypes[$j]",$oport->getSourceLocation());
      }
	}
	else {
	    # this should never occur
	    SPL::CodeGen::errorln("Model input field $infields[$j] of type: 
	      @infieldtypes[$j] is not a valid type",$oport->getSourceLocation());  
	}
	
  #### now generate the right code
  
  if ($scpptyp ne 'SPL::rstring') {
    print "const $scpptyp & local_$j = $sadaptedcpp ;\n";
  	print "myBuf.row[0] [$j] = (void*) &(local_$j);\n";
  } else {
   	print "const char* local_$j =  $sadaptedcpp.c_str();\n";
   	print "myBuf.row[0] [$j] = (void*) (local_$j);\n";
   }
 }   #end of for loop 
%>

您可以看到,此处对参数值进行了一些验证,先将元组属性值列表的大小与输入模型字段比较,然后与操作符所需的端口数比较。而流属性到模型输入字段的实际映射是通过检查有效类型映射来完成的。最后,用来在执行模型期间加载可访问存储位置的 C++ 代码是从输入元组进行填充的。

处理输出字段

清单 15 显示从模型检索输出字段并用它们填充输出元组的用法。

清单 15. 处理输出字段
<%
  my $oport = $model->getOutputPortAt(0);
  my $numAttrs = $oport->getNumberOfAttributes();
  for (my $i=0; $i < $numAttrs; $i++) {
    my $attribute = $oport->getAttributeAt($i);
    my $name = $attribute->getName();
    if ($attribute->hasAssignmentWithOutputFunction()) {
      my $of = $attribute->getAssignmentOutputFunctionName();
      if ($of eq 'fromModel') {
        my $exp = $attribute->getAssignmentOutputFunctionParameterValueAt(0);
        my $splexp = $exp->getSPLExpression();
        $splexp =~ s/^"(.*)"$/$1/; #strip off the enclosing double quotes 
        $splexp =~ tr/-$ ./_/;  # convert invalid chars to _ for valid c++ expression
        print "if (currentOutBuf->_missing_$splexp == true) {\n"; # if missing  
        # now check if optional default value expressions was provided
        my $exp2 = $attribute->getAssignmentOutputFunctionParameterValueAt(1);
        my $cppexp2;
        	     
        if ($exp2 ne '') {
          $cppexp2 = $exp2->getCppExpression();
          my $adaptedcpp2 = SPL::CodeGen::adaptCppExpression($cppexp2, "tp");
          print "SPLLOG(L_INFO, \"setting $splexp\", \"MPK\");\n";
          print "SPLLOG(L_INFO, \"default expression is $adaptedcpp2\", \"MPK\");\n";
          print "  otuple.set_$name($adaptedcpp2);\n";
        } 
        print " } else {\n"; # not missing
        print "  otuple.set_$name(currentOutBuf->$splexp);\n"; 
        print " } \n";
      }  
    } 
  }
%>

您可以看到,此处已经对输出元组赋值进行了评估,对于使用自定义输出函数指出是从模型输出填充自身的那些元组,也生成了用来为元组属性赋值的 C++ 赋值语句。

泛型运算符的代码生成模板的实现已经完成。下一步,我们将查看如何在 SPL 应用程序中使用运算符。


使用运算符

在 InfoSphere Streams 应用程序中使用评分模型运算符

还是和 在 InfoSphere Streams 中集成 SPSS Model Scoring,第 1 部分:从 InfoSphere Streams 运算符调用 Solution Publisher 一样,我们将使用一个简单的 SPL 应用程序示例来演示如何将预测模型集成到 Streams 应用程序中。我们会通过使用包含评分行的文件来进行模拟,并使用 InfoSphere Streams FileSource 运算符来读取信息,生成这些元组的流。我们仍然采用每次写入一个元组的方式,使用 InfoSphere Streams FileSink 运算符将评分元组写入输出文件中。

我们使用了和第 1 部分一样的 basket rule 模型,它有两项输入:性别(字符串值 M 或 F)和收入(整数值),生成一个字符串 M 或 F 的输出,指示输入的数据指出了人们购买啤酒、豆类和披萨组合的偏好倾向。它还生成一个浮点数来表示预测的信心。这次,输入数据文件有点不同:我们有一个包含两个值的文件,而不是只有一个值来表示收入,这两个值是必须同时添加的、用来生产模型所需的收入输入值的基本工资和奖金工资。

运行 SPL 应用程序样例

要求和安装

  1. 为了能构建和运行应用程序样例,需要一个运行正常的 InfoSphere Streams 环境。
  2. 需要在此环境中安装 IBM SPSS Modeler Solution Publisher Runtime 14.2 fixpack 1 和 Solution Publisher hot fix(于 2011 年 10 月 14 日发布)。
  3. 还要保证在部署 Streams 的所有系统上设置了 LD_LIBRARY_PATH,从而确保包含必要的 Solution Publisher 库。

LD_LIBRARY_PATH 要求

假设 Solution Publisher 安装在 $INSTALL_PATH 中,LD_LIBRARY_PATH 需要包含以下内容:

  • $INSTALL_PATH
  • $INSTALL_PATH/ext/bin/*
  • $INSTALL_PATH/jre/bin/classic
  • $INSTALL_PATH/jre/bin

在名为 ldlibrarypath.sh 的 ZIP 文件中提供了一段脚本来正确设置路径。如果 Solution Publisher 不是安装在默认路径下,那么在使用脚本前,需要修改脚本第一行,使其指向 Solution Publisher 安装目录。例如,如果 Solution Publisher 安装在 /homes/hny1/koranda/IBM/SPSS/ModelerSolutionPublisher64P/14.2 中,那么设置应为 CLEMRUNTIME=/homes/hny1/koranda/IBM/SPSS/ModelerSolutionPublisher64P/14.2。

CLEMRUNTIME=/homes/hny1/koranda/IBM/SPSS/ModelerSolutionPublisher64P/14.2

样例内容

ZIP 文件样例包含了来自 Market Basket Analysis 样例的 .pim、.par 和 XML 文件,并加入了评分分支、输入样例和预计输出文件,还有一个完整的 Streams Programming Language 应用程序 Main.spl,包括对 Market Basket Analysis 模型评分的泛型运算符 SPSimple。

在 com.ibm.mpk/SPGeneric/Main.spl 中提供了一个 SPL 应用程序样例,如图 1 所示。

图 1. SPL 应用程序
图片显示 SPL 应用程序带有 FileSource、SPSimple 运算符和 FileSink

调整并编译样例

要运行 SPL 应用程序样例,将 SPGeneric.zip 文件(参见 下载)解压到安装 InfoSphere Streams 和 Solution Publisher 的 Linux 系统。如果 Solution Publisher 安装路径不是默认路径 /opt/IBM/SPSS/ModelerSolutionPublisher/14.2,则需要修改 com.ibm.mpk/SPSimple 目录中的运算符 XML 文件 (SPSimple.xml)。修改 libPathincludePath 条目,使之匹配 Solution Publisher 安装位置:

<cmn:libPath>/opt/IBM/SPSS/ModelerSolutionPublisher/14.2</cmn:libPath>
<cmn:includePath>/opt/IBM/SPSS/
    ModelerSolutionPublisher/14.2/clemrtl/include</cmn:includePath>

还需要修改 Main.spl 文件,在运算符的调用中添加 SP_Install 参数。

清单 16. 修改 Main.spl
stream<DataSchemaPlus> scorer = SPSimple(data){
  param 
    pimfile:"baskrule.pim";
    parfile:"baskrule.par";
    xmlfile:"baskrule.xml";
    SP_Install:"/homes/hny1/koranda/IBM/SPSS/ModelerSolutionPublisher64P/14.2";
    modelFields:"sex","income";
    streamAttributes: s_sex, baseSalary+bonusSalary;
    	  
  output 
    	  scorer: 
    	    income = fromModel("income"),
    	    predLabel = fromModel("$C-beer_beans_pizza"), 
    	    confidence = fromModel("$CC-beer_beans_pizza");	
    }

请注意 SPL 程序中其他一些事项。

清单 17. 模型的输入值
modelFields:"sex","income";
    	  streamAttributes: s_sex, baseSalary+bonusSalary;

s_sex 属性用作模型的第一输入值,baseSalarybonusSalary 用作其他的输入值。还要注意以下内容。

清单 18. 输出元组
output scorer: 
    	    income = fromModel("income"),
    	    predLabel = fromModel("$C-beer_beans_pizza"), 
    	    confidence = fromModel("$CC-beer_beans_pizza");

模型生成的收入值(根据输入值)、预测值和信息都被添加到输出元组中。

编译样例

要将此样例编译成单独的 Streams 应用程序,请将目录改成解压项目样例 (SPGeneric) 的位置,并运行 make 命令,如下所示。

清单 19. 编译样例
bash-3.2$ cd STSP2Test/SPGeneric/
bash-3.2$ make
/homes/hny1/koranda/InfoSphereStreams64/bin/sc 
  --output-directory="output/com.ibm.mpk.Main/Standalone" --data-directory="data" -T 
  -M com.ibm.mpk::Main  --no-toolkit-indexing --no-mixed-mode-preprocessing 
Creating types...
Creating functions...
Creating operators...
Creating PEs...
Creating standalone app...
Creating application model...
Building binaries...
 [CXX-type] tuple<string s_sex,int64 
       baseSalary,int64 bonusSalary,in...,float64 confidence>
 [CXX-operator] data
 [CXX-operator] scorer
 [CXX-operator] Writer
 [CXX-type] tuple<rstring s_sex,int64 baseSalary,int64 bonusSalary>
 [CXX-pe] pe0
 [CXX-standalone] standalone
 [LD-standalone] standalone
 [LN-standalone] standalone 
 [LD-pe] pe0
bash-3.2$

执行样例

要执行应用程序,应确保已设置了 LD_LIBRARY_PATH。这里可以看到设置并回显路径以验证路径是否设置正确的命令。

清单 20. 执行样例
bash-3.2$ source ldlibrarypath.sh 
bash-3.2$ echo $LD_LIBRARY_PATH
/homes/hny1/koranda/IBM/SPSS/ModelerSolutionPublisher64P/14.2:/homes/hny1/koranda/IBM
/SPSS/ModelerSolutionPublisher64P/14.2/ext/bin/pasw.adp:/homes/hny1/koranda/IBM/SPSS/
ModelerSolutionPublisher64P/14.2/ext/bin/pasw.alm:/homes/hny1/koranda/IBM/SPSS/Modeler
SolutionPublisher64P/14.2/ext/bin/pasw.bagging:/homes/hny1/koranda/IBM/SPSS/ModelerSo
lutionPublisher64P/14.2/ext/bin/pasw.boosting:/homes/hny1/koranda/IBM/SPSS/ModelerSolu
tionPublisher64P/14.2/ext/bin/pasw.cognos:/homes/hny1/koranda/IBM/SPSS/ModelerSolutio
nPublisher64P/14.2/ext/bin/pasw.common:/homes/hny1/koranda/IBM/SPSS/ModelerSolution
Publisher64P/14.2/ext/bin/pasw.me:/homes/hny1/koranda/IBM/SPSS/ModelerSolutionPublish
er64P/14.2/ext/bin/pasw.netezzaindb:/homes/hny1/koranda/IBM/SPSS/ModelerSolutionPubl
isher64P/14.2/ext/bin/paswneuralnet:/homes/hny1/koranda/IBM/SPSS/ModelerSolutionPubli
sher64P/14.2/ext/bin/pasw.outerpartition:/homes/hny1/koranda/IBM/SPSS/ModelerSolutionP
ublisher64P/14.2/ext/bin/pasw.pmmlmerge:/homes/hny1/koranda/IBM/SPSS/ModelerSolutio
nPublisher64P/14.2/ext/bin/pasw.psm:/homes/hny1/koranda/IBM/SPSS/ModelerSolutionPub
lisher64P/14.2/ext/bin/pasw.scoring:/homes/hny1/koranda/IBM/SPSS/ModelerSolutionPubli
sher64P/14.2/ext/bin/pasw.split:/homes/hny1/koranda/IBM/SPSS/ModelerSolutionPublisher
64P/14.2/ext/bin/pasw.transformation:/homes/hny1/koranda/IBM/SPSS/ModelerSolutionPub
lisher64P/14.2/ext/bin/pasw.tree:/homes/hny1/koranda/IBM/SPSS/ModelerSolutionPublisher
64P/14.2/ext/bin/pasw.vi:/homes/hny1/koranda/IBM/SPSS/ModelerSolutionPublisher64P/14.
2/ext/bin/pasw.xmldata:/homes/hny1/koranda/IBM/SPSS/ModelerSolutionPublisher64P/14.2
/ext/bin/spss.bayesiannetwork:/homes/hny1/koranda/IBM/SPSS/ModelerSolutionPublisher
64P/14.2/ext/bin/spss.binning:/homes/hny1/koranda/IBM/SPSS/ModelerSolutionPublisher6
4P/14.2/ext/bin/spss.C5:/homes/hny1/koranda/IBM/SPSS/ModelerSolutionPublisher64P/14.
2/ext/bin/spss.inlinecsp:/homes/hny1/koranda/IBM/SPSS/ModelerSolutionPublisher64P/14.
2/ext/bin/spss.knn:/homes/hny1/koranda/IBM/SPSS/ModelerSolutionPublisher64P/14.2/ex
t/bin/spss.modelaccreditation:/homes/hny1/koranda/IBM/SPSS/ModelerSolutionPublisher6
4P/14.2/ext/bin/spss.modelevaluation:/homes/hny1/koranda/IBM/SPSS/ModelerSolutionPub
lisher64P/14.2/ext/bin/spss.predictoreffectiveness:/homes/hny1/koranda/IBM/SPSS/Model
erSolutionPublisher64P/14.2/ext/bin/spss.predictorstat:/homes/hny1/koranda/IBM/SPSS/M
odelerSolutionPublisher64P/14.2/ext/bin/spss.propensitymodelling:/homes/hny1/koranda/I
BM/SPSS/ModelerSolutionPublisher64P/14.2/ext/bin/spss.psmmodel:/homes/hny1/koranda
/IBM/SPSS/ModelerSolutionPublisher64P/14.2/ext/bin/spss.selflearning:/homes/hny1/kora
nda/IBM/SPSS/ModelerSolutionPublisher64P/14.2/ext/bin/spss.svm:/homes/hny1/koranda
/IBM/SPSS/ModelerSolutionPublisher64P/14.2/ext/bin/spss.xd:/homes/hny1/koranda/IBM/S
PSS/ModelerSolutionPublisher64P/14.2/jre/bin/classic:/homes/hny1/koranda/IBM/SPSS/Mo
delerSolutionPublisher64P/14.2/jre/bin
bash-3.2$

在下一步中,将目录更改为 SPGeneric 下的数据目录,并执行独立的应用程序,如下所示:

bash-3.2$ cd data/
bash-3.2$ ../output/com.ibm.mpk.Main/Standalone/bin/standalone

由于 SPL 程序设置了 INFO 的详细程度,所以您会看到很多现实过程的信息,如下所示。

请注意:为了便于阅读,我们删除了时间戳、过程、类、方法和行信息。

22 Aug 2011 15:39:53.571 [21029] 
INFO spl_pe M[PEImpl.cpp:process:483]   - Start processing...
清单 21. 提示信息
bash-3.2$ ../output/com.ibm.mpk.Main/Standalone/bin/standalone 
- Start processing...
- Using a pimfile value of: "baskrule.pim"
- Using a parfile value of: "baskrule.par"
- About to clemrtl initialise using SP_Install of: "/homes/hny1/koranda/IBM/
      SPSS/ModelerSolutionPublisher64P/14.2"
- After clemrtl initialise
- Major=14 Minor=2 Release=0 build=0
- Image Handle Retrieved: 1
- About to get field count
- Field Count is: 2
- About to get Field Types
- Field Type 0 is: STRING
- Field Type 1 is: LONG
- About to get Output Field Count
- Output Field Count is: 4
- About to get output field types
- Field Type 0 is: STRING
- Field Type 1 is: LONG
- Field Type 2 is: STRING
- Field Type 3 is: DOUBLE
- About to set alternative Input
- After Set Alternative input
- About to set alternative output
- After Set Alternative Output
 - About to prepare
- After Prepare
- Leaving Constructor
 - Opening ports...
- Opened all ports...
- Creating 1 operator threads
 - Created 1 operator threads
- Joining operator threads...
- Processing tuple from input port 0 {s_sex="F",baseSalary=5000,bonusSalary=5000}
 - About to execute the image
- In next_record iterator
- In next_record iterator
- After Execute
- Sending tuple to output port 0 {s_sex="F",baseSalary=5000,
     bonusSalary=5000,income=10000,predLabel="F",confidence=0.988327}}
...
lines omitted
...
- Joined all operator threads...
- Joining window threads...
- Joined all window threads.
- Joining active queues...
- Joined active queues.
- Closing ports...
- Closed all ports...
- Notifying operators of termination...
 - Notified all operators of termination...
- Flushing operator profile metrics...
- Flushed all operator profile metrics...
- Deleting active queues...
- Deleted active queues.
- Deleting input port tuple cache...
- Deleted input port tuple cache.
- Deleting all operators...
- About to Close Image with handle1
- After Close Image
- Deleted all operators.
- Terminating...
- Completed the standalone application processing
- Leaving MyApplication::run()
- Shutdown request received by PE...
- shutdownRequested set to true...

要查看所有生成的结果,请查阅由 FileSink 创建的 mpkoutput.csv 文件。请注意,输出文件包含基本工资和奖金工资的输入值,以及来自模型的合并收入值。

清单 22. 结果
bash-3.2$ cat mpkoutput.csv 
"F",5000,5000,10000,"F",0.988326848249027
"F",15000,5000,20000,"F",0.989645351835357
"F",10000,5000,15000,"F",0.988326848249027
"F",20000,5000,25000,"F",0.989645351835357
"F",5000,5000,10000,"F",0.988326848249027
"F",15000,5000,20000,"F",0.989645351835357
"F",10000,5000,15000,"F",0.988326848249027
"F",20000,5000,25000,"F",0.989645351835357
"M",5000,5000,10000,"T",0.838323353293413
"M",15000,5000,20000,"F",0.990963855421687
"M",10000,5000,15000,"T",0.838323353293413
"M",20000,5000,25000,"F",0.990963855421687
"M",5000,5000,10000,"T",0.838323353293413
"M",15000,5000,20000,"F",0.990963855421687
"M",10000,5000,15000,"T",0.838323353293413
"M",20000,5000,25000,"F",0.990963855421687
bash-3.2$

成功了!

您已经了解到如何将单一用途的运算符转换成泛型运算符,Streams 应用程序开发人员能够按需使用它,对多种不同的 SPSS 预测模型进行评分。


结束语

结束语

本教程演示了如何使用泛型运算符将 SPSS Modeler 预测分析的执行打包(符合 Solution Publisher API 限制,可以一次执行一个元组)。Streams 应用程序开发人员可以方便地使用该泛型运算符对流数据执行预测模型。

请注意,还有其他通过 PMML 和 Streams Mining 工具包在 InfoSphere Streams 中执行评分模型的方法。与 Mining Toolkit 的 PMML 集成所支持的模型相比,此处介绍的直接打包技术和通过 Solution Publisher 接口集成 SPSS 模型集成的方法可以将评分扩展至更多的模型。


下载

描述名字大小
泛型操作符、程序、模型和数据样例SPGeneric.zip26KB

参考资料

学习

获得产品和技术

讨论

条评论

developerWorks: 登录

标有星(*)号的字段是必填字段。


需要一个 IBM ID?
忘记 IBM ID?


忘记密码?
更改您的密码

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件

 


在您首次登录 developerWorks 时,会为您创建一份个人概要。您的个人概要中的信息(您的姓名、国家/地区,以及公司名称)是公开显示的,而且会随着您发布的任何内容一起显示,除非您选择隐藏您的公司名称。您可以随时更新您的 IBM 帐户。

所有提交的信息确保安全。

选择您的昵称



当您初次登录到 developerWorks 时,将会为您创建一份概要信息,您需要指定一个昵称。您的昵称将和您在 developerWorks 发布的内容显示在一起。

昵称长度在 3 至 31 个字符之间。 您的昵称在 developerWorks 社区中必须是唯一的,并且出于隐私保护的原因,不能是您的电子邮件地址。

标有星(*)号的字段是必填字段。

(昵称长度在 3 至 31 个字符之间)

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件.

 


所有提交的信息确保安全。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=Information Management
ArticleID=790158
ArticleTitle=在 InfoSphere Streams 中集成 SPSS Model Scoring,第 2 部分: 使用泛型运算符
publish-date=01302012