本文是对英文原文SQL Server Data Mining Managed Plug-In Algorithms Tutorial的部分翻译及整理,主要是描述SSAS数据挖掘算法的基本扩展方法和开发过程。本文的内容只是原文的一部分,如果想了解更多信息可以下载原文。英文原文在本文附件中下载。

  SSAS为我们提供了九种数据挖掘算法,但是在应用中我们需要根据实际问题设计适当的算法,这个时候就需要扩展SSAS,使它能应用更多的算法,SSAS有比较好的可扩展性,它提供了一个完整的机制来进行扩展,只要继承一些类并按适当的方法进行注册就可以在SSAS中使用自己的算法了。下面我将通过实例分别用几篇文章来介绍一下如何开发SSAS算法插件。本文介绍的算法插件开发方法是基于托管代码的,是用C#开发的(算法插件也可以用C++开发,并且SQLSERVER2005的案例中附带C++版本的代码stub)。整个过程大至为六个步骤。在开始开发之前需要做一些准备工作,就是要去下载一个用C++编写的COM组件,叫DMPluginWrapper(可以通过下载本文附带的附件获得),它作为SSAS与算法插件的中间层,用于处理SSAS与算法插件之间的交互以及封装从SSAS到算法插件的参数和从算法插件到SSAS的处理结果。DMPluginWrapper、SSAS和算法插件之间的关系可以由下图来描述。

 

SQL server 数据收集功能和SQL Server Profiler sql server提供哪些数据挖掘算法_SSAS

图表 1: DMPluginWrapper、SSAS和算法插件之间的关系

 

  下面开始创建算法扩展的项目。

  首先新建一个类库项目(名为AlgorithmPlugin)将刚才的DMPluginWrapper项目引用到新建的这个AlgorithmPlugin类库项目中。你可以选择为这个类库项目进行程序集签名,这样就可以将其注册到GAC中。另外还要为DMPluginWrapper添加后生成脚本将程序集注册到GAC,参考脚本如下(根据机器具体设置而定):

Code
"C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\RegAsm.exe" $(TargetPath) 
"C:\Program Files\Microsoft Visual Studio 8\SDK\v2.0\Bin\gacutil.exe" /u $(TargetName) 
"C:\Program Files\Microsoft Visual Studio 8\SDK\v2.0\Bin\gacutil.exe" /if $(TargetPath)

 

如果第一行脚本不能正确运行的话,算法插件是不能被SQLSERVER分析服务器识别的。另外两行脚本就是将算法程序集注册到GAC。

  接下来的几个步骤主要是继承一些基类的工作,包括AlgorithmMetadataBase类、AlgorithmBase类和ICaseProcessor接口和AlgorithmNavigationBase类。首先在AlgorithmPlugin中新建一个类文件并命名为Metadata,为这个类添加ComVisible、MiningAlgorithmClass(typeof(Algorithm))和Guid属性(Algorithm是下面要创建的算法类),并为Guid属性指定一个GUID编码。这个类要继承于AlgorithmMetadataBase类。现在要做的事情就是覆盖基类的方法。下面是所有需要覆盖的方法(对于较简单的实现写在表格中):

 

方法名

实现(参考)

备注

GetServiceName

return "MyFirstAlgorithmPlugin"

这个方法的返回值中不能带有空格字符

GetServiceDescription

return "Sample Algorithm Plugin";

 

GetServiceType

PlugInServiceType.ServiceTypeOther;

 

GetViewerType

return string.Empty

 

GetScaling

return MiningScaling.Medium;

用于指定算法适用的规模,这个值不会被服务器使用而是显示在模式行集中,为用户提供算法的一些相关信息。

GetTrainingComplexity

return MiningTrainingComplexity.Low

用于指定算法训练适用的复杂度,这个值不会被服务器使用而是显示在模式行集中,为用户提供算法的一些相关信息。

GetPredictionComplexity

return MiningPredictionComplexity.Low

用于指定预测复杂度,这个值不会被服务器使用而是显示在模式行集中,为用户提供算法的一些相关信息。

GetSupportsDMDimensions

retrun false;

 

GetSupportsDrillThrough

return false;

指定这个算法是否支持钻透功能。

GetDrillThroughMustIncludeChildren

return false;

 

GetCaseIdModeled

return false;

 

GetMarginalRequirements

return MarginalRequirements.AllStats

 

GetParametersCollection

return null;

算法参数,因为本文中的例子没有参数,所以这里返回空。

GetSupInputContentTypes

MiningColumnContent[] arInputContentTypes = new MiningColumnContent[]

{

MiningColumnContent.Discrete,

MiningColumnContent.Continuous,

MiningColumnContent.Discretized,

MiningColumnContent.NestedTable,

MiningColumnContent.Key

};

 

return

指定算法所支持的输入属性的数据类型,如连续型、离散型等。

GetSupPredictContentTypes

MiningColumnContent[] arPredictContentTypes = new MiningColumnContent[]

{

MiningColumnContent.Discrete,

MiningColumnContent.Continuous,

MiningColumnContent.Discretized,

MiningColumnContent.NestedTable,

MiningColumnContent.Key

};

return

与上一个方法类似,这里是指定预测属性所支持的数据类型。

GetSupportedStandardFunctions

SupportedFunction[] arFuncs

= new SupportedFunction[] {

SupportedFunction.PredictSupport,

SupportedFunction.PredictHistogram,

SupportedFunction.PredictProbability,

SupportedFunction.PredictAdjustedProbability,

SupportedFunction.PredictAssociation,

SupportedFunction.PredictStdDev,

SupportedFunction.PredictVariance,

SupportedFunction.RangeMax,

SupportedFunction.RangeMid,

SupportedFunction.RangeMin,

SupportedFunction.DAdjustedProbability,

SupportedFunction.DProbability,

SupportedFunction.DStdDev,

SupportedFunction.DSupport,

SupportedFunction.DVariance,

// content-related functions

SupportedFunction.IsDescendent,

SupportedFunction.PredictNodeId,

SupportedFunction.IsInNode,

SupportedFunction.DNodeId,

};

return

指定DMX所支持的函数。

CreateAlgorithm

returnnew Algorithm();

返回算法实例,Algorithm是接下来要创建的类。


 

TaskProgressNotification trainingProgress。这个类包含了算法主要的处理逻辑。下面是要实现的方法:

方法名

//处理样本

InsertCases

参考实现


Code
//遍历所有的样本并且每处理100个样本更新一次处理进度。 
trainingProgress = this.Model.CreateTaskNotification(); 

// 设置当前的处理进度为0 
trainingProgress.Current = 0; 

// 取得总的样本数量。 
trainingProgress.Total = 

    (int)this.MarginalStats.GetTotalCasesCount(); 

// 为跟踪提示信息设置格式字符串 
trainingProgress.Format = "Processing cases: {0} out of {1}"; 

// 开始处理 
trainingProgress.Start(); 
bool success = false; 
try 

    { 

    caseSet.StartCases(this); 

    success = true; 

    } 
finally 

    { 

    trainingProgress.End(success); 

    }    
 
方法名:ProcessCase 
参考实现:
    
Code
// 检查并确认处理过程没有被中断。
this.Context.CheckCancelled(); 

// 更新当前的进度值 
trainingProgress.Current++;  

if (caseId % 100 == 0) 


    { 


    trainingProgress.Progress(); 


    } 

//TODO: 在这里进行实际的模型训练处理逻辑


 

方法名:SaveContent

参考实现


Code
//创建一个自定义的标签内容用于保存处理结果(其结构类似XML),MyPersistenceTag是自定义的枚举类型 


    writer.OpenScope((PersistItemTag)MyPersistenceTag.ShellAlgorithmContent); 

    writer.SetValue(System.DateTime.Now); 

    writer.SetAttribute((PersistItemTag)MyPersistenceTag.NumberOfCases, 
this.MarginalStats.GetTotalCasesCount()); 

    writer.CloseScope();



方法名:LoadContent

参考实现


Code
//打开自定义的标签(与SaveContent方法相对应)
reader.OpenScope((PersistItemTag)MyPersistenceTag.ShellAlgorithmContent); 

//读取处理时间 


    System.DateTime processingTime; 

    reader.GetValue(out processingTime); 

// 取得处理的样本数量 
uint numberCases = 0; 


    reader.GetAttribute((PersistItemTag)MyPersistenceTag.NumberOfCases, out numberCases); 

    reader.CloseScope();


 

方法名:Predict

参考实现


Code
AttributeGroup targetAttributes = predictionResult.OutputAttributes; 

    targetAttributes.Reset(); 
uint nAtt = AttributeSet.Unspecified;  

//对于每一个目标属性,从训练集中复制预测结果 
while (targetAttributes.Next(out nAtt)) 

    { 

         //创建一个AttributeStatistics对象用于保存对当前目标属性的预测结果 
    AttributeStatistics result = new AttributeStatistics(); 


        //设置预测结果中的目标属性,即当前的预测结果针对于哪个输入属性 
    result.Attribute = nAtt; 


        // 取得当前属性的概率统计值,也即通过模型训练得到的边缘统计概率。 
   AttributeStatistics trainingStats = this.MarginalStats.GetAttributeStats(nAtt); 


        //复制其余的数据到结果对象 
   result.AdjustedProbability = trainingStats.AdjustedProbability; 

        result.Max = trainingStats.Max; 

        result.Min = trainingStats.Min; 

        result.Probability = trainingStats.Probability; 

        result.Support = trainingStats.Support;  


        //复制状态统计到结果对象中 
    if (predictionResult.IncludeStatistics) 

        { 

            for ( int nIndex = 0; nIndex < trainingStats.StateStatistics.Count; nIndex++) 

    { 

        bool bAddThisState = true; 


        // 如果是丢失值状态,那么只有当需要的时候才将其包含在结果之中。 
   if (trainingStats.StateStatistics[0].Value.IsMissing) 

        {

            bAddThisState = predictionResult.IncludeMissingState; 

        }  


        if (bAddThisState) 

        { 

            result.StateStatistics.Add( 

                trainingStats.StateStatistics[(uint)nIndex]); 

        } 

    } 

    } 

//如果预测需要内容结点,就要为内容结点设置一个唯一的编号
if (predictionResult.IncludeNodeId) 

    { 

        result.NodeId = "000"; 

    }

    predictionResult.AddPrediction(result);


 

方法名:GetNavigator

参考实现:    


Code
//AlgorithmNavigator是下面要创建的类 
return new AlgorithmNavigator(this, forDMDimensionContent);


 

 

  接下来要实现的是AlgorithmNavigator类,这个类要继承于AlgorithmNavigationBase。这个类主要用于显示算法处理结果中所有结点的信息。在这个类中有三个成员变量:Algorithm类型的algorithm、bool类型的forDMDimension和int类型的currentNode。下面是这个类要实现的方法:

    方法名(构造方法):AlgorithmNavigator

参考实现


Code
this.algorithm = currentAlgorithm; 
this.forDMDimension = dmDimension; 
this.currentNode = 0;


 


方法名

实现

备注

MoveToNextTree

return false;

 

GetCurrentNodeId

return currentNode;

 

ValidateNodeId

return (nodeId == 0);

 

LocateNode

if

return false;

currentNode = nodeId;

return true;

 

GetNodeIdFromUniqueName

int nNode = Convert.ToInt32(nodeUniqueName);

return

 

GetUniqueNameFromNodeId

return nodeId.ToString("D3");

按三位数字的格式输出结点编号

GetParentCount

return

 

GetParentNodeId

return

 

GetChildrenCount

return

 

GetChildNodeId

return

 

GetNodeType

return NodeType.Model;

 

GetNodeUniqueName

return

 

GetNodeAttributes

return null;

 


 

方法名

//此方法返回了描述结点的数值特征

GetDoubleNodeProperty

参考实现

 


Code
double dRet = 0; 
double dTotalSupport = lgorithm.MarginalStats.GetTotalCasesCount(); 
double dNodeSupport = 0.0; 

        dNodeSupport = dTotalSupport; 
switch (property) 

        { 
//结点的支持度 
case NodeProperty.Support: 

        dRet = dNodeSupport; 
break; 
case NodeProperty.Score: 

        dRet = 0; 

            break; 

                 //结点概率 
       case NodeProperty.Probability: 

                 dRet = dNodeSupport / dTotalSupport; 

            break; 

                 //结点的边缘概率 
       case NodeProperty.MarginalProbability: 

                 dRet = dNodeSupport / dTotalSupport; 

                 break; 

            } 
return dRet;         
     
    方法名: 
  //取得结点的字符串表示 
  GetStringNodeProperty

    参考实现:     


Code
string strRet = ""; 
switch (property) 

        { 

            case NodeProperty.Caption: 

            { 

                strRet = algorithm.Model.FindNodeCaption(GetNodeUniqueName()); 

                if (strRet.Length == 0) 

                {

                    strRet = "All"; 

                }

            }

            break; 

            

            case NodeProperty.ConditionXml: 

            strRet = ""; 

            break; 

            

            case NodeProperty.Description: 

            strRet = "All Cases"; 

            break; 

            

            case NodeProperty.ModelColumnName: 

            strRet = ""; 

            break; 

            

            case NodeProperty.RuleXml: 

            strRet = ""; 

            break; 

            

            case NodeProperty.ShortCaption:     

            strRet = "All"; 

            break; 

        } 
return strRet;


 

 

    方法名

  //取得结点的分布

  GetNodeDistribution

参考实现

 


Code
int attStats = (int)algorithm.AttributeSet.GetAttributeCount(); 

        AttributeStatistics[] marginalStats = new AttributeStatistics[attStats]; 
for (uint nIndex = 0; nIndex < attStats; nIndex++) 

        { 

            marginalStats[nIndex] = algorithm.MarginalStats.GetAttributeStats(nIndex); 

        } 
return marginalStats;


        

  现在我们已经实现了完所有需要要实现的类,最后要做的就是将算法插件部署到分析服务器。在完成代码后,需要将程序集注册到GAC以便分析服务器可以从中加载插件。下面的代码就是将DMPluginWrapper加载到GAC的脚本,如果在本文的开头正确地在Visual Studio中设置了后生成(Post-Building)脚本的话可以跳过下面的脚本代码,因为它们是相同的功能:    


Code
//将DMPluginWrapper.dll注册到GAC中 
"%ProgramFiles%\Microsoft Visual Studio 8\SDK\v2.0\Bin\gacutil.exe" /u DMPluginWrapper 

"%ProgramFiles%\Microsoft Visual Studio 8\SDK\v2.0\Bin\gacutil.exe" /if DMPluginWrapper.dll      


            //将AlgorithmPluging.dll注册到GAC中并通过REGASM将程序集注册到注册表中使其可以用于COM调用 
"%WINDIR%\Microsoft.NET\Framework\v2.0.50727\RegAsm.exe" AlgorithmPlugin.dll 

"%ProgramFiles%\Microsoft Visual Studio 8\SDK\v2.0\Bin\gacutil.exe" /u AlgorithmPlugin 

"%ProgramFiles%\Microsoft Visual Studio 8\SDK\v2.0\Bin\gacutil.exe" /if AlgorithmPlugin.dll


    

  注意以上的路径一定要与自己机器的设置匹配。成功运行上面的脚本后算法插件就被注册到计算机之中,但要在分析服务器中使用这个算法还有一项工作,就是把在分析服务器中注册这个算法。在分析服务器中注册算法有两种方式,一种是通过发送XMLA代码到分析服务器来注册;另一种是通过修改SQLSERVER的配置文件来注册算法插件。下面先说第一种:  

通过XMLA方式注册算法插件——

 


Code
<!--

        Template for registering a plug-in algorithm 

        Replace MyPluginAlgorithm with the ServiceName of your algorithm 

        Replace 00000000-0000-0000-0000-000000000000 with the Guid of your Algorithm 

        After deploying, you will need to restart the server to load the plug-in 
-->
<Alter 
AllowCreate="true" 

        ObjectExpansion="ObjectProperties" 

        xmlns="http://schemas.microsoft.com/analysisservices/2003/engine"> 
<Object /> 
<ObjectDefinition> 
<Server 
xmlns:xsd="http://www.w3.org/2001/XMLSchema" 

        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> 
<Name>.</Name> 
<ServerProperties> 
<ServerProperty> 

            <!-- 用自己的算法名称替换 --> 

            <Name>MyFirstAlgorithmPlugin</Name> 


            <!-- 是否启用算法 --> 

            <Value>true</Value> 
</ServerProperty> 
<ServerProperty> 

<!-- 用自己的算法名称替换 --> 
<Name>MyFirstAlgorithmPlugin</Name> 
<!--算法的GUID(在算法类中指定的GUID) -->
<Value>00000000-0000-0000-0000-000000000000</Value> 
</ServerProperty> 
</ServerProperties>
</Server> 
</ObjectDefinition> 
</Alter>

 

通过修改SQLSERVER的配置文件注册算法——

Code<ConfigurationSettings>
 
<DataMining> 
            
 
<Algorithms> 
            
 
<!-- 结点中的字符串应该是算法名称 --> 
<AlgorithmPlugin> 
            <!-- 是否启用算法 --> 
            <Enabled>1</Enabled> 
            <!--算法的GUID (在算法类中指定的GUID)--> 
            <CLSID>00000000-0000-0000-0000-000000000000</CLSID> 
</AlgorithmPlugin>

 

 

  在以上的配置信息中算法名称是来自于Metadata类的GetServiceName这个方法,即在配置中设置的算法名称要与这个方法的返回值相同。到这里一个基本的算法插件程序就完成了,重启分析服务器后就可以在建立挖掘模型的窗口中看到新的算法出现在算法下拉列表中了。

  从建立算法插件的整个过程来看,除开前后的准备工作和部署主要就是对三个类的重写,而其中最主要的类就是算法类的重写,其它两个类的主要作用是为算法提供元数据信息及结果的描述。在算法类中要实现挖掘模型的样本训练以及预测,这是算法最关键的地方,重写算法类中的ProcessCase方法和Predict方法是算法插件的核心。在了解如何扩展算法后要做的工作就是设计新的算法或将已经完成的算法集成到插件中。